Canvas Widget - Take 2

12 April 2004, 22:28 UTC

After playing with some code to try to make a canvas widget, I realised I was missing some important ideas. So I will try to write down a new set of thoughts.

One observation I made was that I should make better use of Python's basic types. For example, there is no point creating a "Colour" type when a tuple of (name,r,g,b,alpha,pixel) will do just as well. Similarly the standard python list should be used for a per-window (or per-pane) display list.

Also, input must be integrated with output. There is no point having a Canvas widget that renders stuff but doesn't accept input.

Finally, the display list could use a bit more structure than just panes containing random text and panes. There is an underlying ordering of most text, and it is worth while allowing that the be reflected usefully in the display list.

Other observations that are useful input to the design process:

So where does that leave us?

Maybe not so very different from where we started except for the lazy cells.

For editable text (As opposed to e.g. labels) we really want individual letters in the display list so that a mouse-click can find them easily. Each letter (glyph) needs a size and a position relative to the start of the list.

We don't want to have to re-render the whole page after any change, but for generality we need to regenerate the display list. We therefore need a 'current' display list and a 'new' display list that we compare looking for differences that need to be rendered.

The 'scroll-window' is a problem without an immediately obvious solution.

Using the scroll-bar we want to be able to select a point some fraction of the way through the document (whether this point is where the display starts or ends or the middle is a separate question). As the document has more structure than simple text, it may not be meaningful to simply count lines.

We might be able to require the parsing process to count or estimate some line-count-like value, but it could be that the width of the display substantially affects the real line count. Possibly the linecount is based on an estimate (80 char lines) and we don't worry about the error. After all, dragging the scoll bar doesn't make for precise positioning - you need line-steps or page-steps for that, and these can be done based on precise knowledge of recent rendering.

So, back the the "separate question" - Exactly what value to we give when we record a scroll-bar position? Document location at top of page? at bottom? at middle? Or maybe where on the page the cursor is?

It think the later is most general. We make it a bit more general by saying that the cursor position includes some rendering context, with the precise meaning of the context dependant on the rendering function. For simple text it might be the screen line, and the amount of horizontal scroll needed.

The rendering function (well, the display-list-generating function) should start with the text around the cursor, render that, and see how much space is left for more context. It will quite possibly take a largish section of the document, certainly a line, possible a paragraph, maybe even a page. Once this is rendered into a display list, some of the list might be found to be outside the window and so it will have to be discarded.

How do we store a point in the document? Just the leaf node is not sufficient was walking back up the tree and understanding context would be awkward. Possibly a list of nodes on the path down the document tree to the leaf, with indexes along the way as hints - if they are wrong it doesn't matter.

We would probably want to flag any node that is listed on a position so that if the node is changed, we can hunt through all positions to find it and make appropriate modifications based on hints in the position.

The Design of the Display List

The key functionality that the display list must provide is:

The display list is made up of display objects.

Each display object contains:

bullet A datum which represents 'this' location in the document. It is provided when the display module is asked to map a x,y co-ordinate to a datum.

As well as display objects, the object list can contain:

These should appear before any text or display objects, and apply to the whole display list.

Redisplay

The image needs to be redisplayed if some area has been damaged or the document has changed. For a change in the document, we first calculate the area (possibly) damaged by that change, and then perform a redisplay of damaged areas.

When the document is changed, the display module asks for a new display list. This display list may well share sub-lists with the original, and may contain function rather than data nodes which must be called as they are found.

The new and old display lists are walked in unison. When a significant difference is found, the areas covering the before and after objects are added to the damaged region.

In some cases, this might be the whole display. In others it might be a single character.

Once we have a current display list and a damaged area, we blank all rectangles in the damaged area, and the walk the display tree finding any object with a bounding rectangle that intersects the damaged area, and we render it clipped to the damaged area. When a list contains possibly overlapping objects, we make a first pass to find how many layers there are, and then another pass for each layer.

As we walk the tree for display, we keep track (on a stack) of the current origin, matrix, font, and foreground colour. Background colour and border information do not need to be tracked, background because it is already drawn, and border because it is not inherited.


point to node mapping

When a UI event such as a mouse click identified a particlar point in the display, we need to make it to a node in the document. This simply involves a recursive walk, mapping the window relative co-ordinates to object-relative and testing for inclusion. The matrix must be applied in reverse before checking if a co-ordinate is in the region. When there are possible overlapping objects, we first find a list of layers and then make one pass for each layer. Though we could optimise that a bit...

If the display-object that is found to contain the point doesn't have an associated document location, we find the closest known location up the tree, and provide a count of how many objects later in the list the final object was.

This information is passed to... some function.

Processing input

Each window provides a function that processes incoming events. These events either go to the document node that currently has the "focus", or they go to the document node identified by an x,y co-ordinate.



[æ]