Canvas Widget - Take 2
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:
BULLET - Using Python call-backs for lazy object evaluation is a core design decision for this project. There should never be any call to do a lot of work in one hit. Instead, a piece of code should do a small or moderate amount of work, and leave incomplete parts of the result as objects that can cause further callbacks to do a bit more of the work. For example, when parsing a large file, we might start with a lazy object (parsefile start (fileobject 0 END)) which is a call to "parsefile" setting the parse-state as "start" and the text to be parsed as the whole of a file, which might not even have been read yet. When "parsefile" is called, it might parse the first paragraph, or function, or some amount of data, and return a structure that is partly parsed but contains further calls to "parsefile" with different parse-state and different text ranges. Creating the display list should be done in exactly this lazy fashion, so there must be room in the structure to store descriptions of as-yet unrendered data, and a well defined mechanism for calling these descriptions. BULLET - Layout is affected by the rendered size of text, which is most apparent to the rendering engine. It seems tempting to perform a provisional layout creating a display list. We then get this sized by the rendering engine, and finally fine-tune the layout based on this size information. This approach seems good for a basic text editor which isn't very fussy about layout and just wants the text to look OK on the screen, but doesn't seem so good for a document formatter which wants precise details so the document can be rendered the same way on the display or on a printer. I think that, with the document formatter in mind, we want to keep these two functions: sizing and drawing, separate. They can certainly use some common data structures, such as a list of characters that are to be juxtaposed one after the other. BULLET - The parameters to the rendering function need to include not only the document and a style, but also - A current cursor position in the document - A scroll-window into the document It is possible that the 'scroll-window' should be a soft attribute of the current position so that when a position is saved prior to a brief excusion, you can get back to exactly where you were before.
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:
- To be able to easily render part or all of the display list
- To be able to map from a screen location back to a document node
The display list is made up of display objects.
Each display object contains:
- x,y co-ordinates relative to a containing object.
- u,d,l,r distances of bounding-box edge from origin
- matrix for linear transforms on the content
- no-overlap flag, set if contents are known not to overlap
- relative depth, aka layer number.
- text-string - used to render text
- object-list - used to gather subobjects