The drawing objects are instances of subclasses of the drawing-object. The term "drawing-object-spec" refers to either a drawing-object or a list of "drawing-object-specs". The drawing objects hierarchy is made of "drawing-object-specs".
The leaf nodes in the hierarchy are drawing-objects which actually do the drawing, typically by calling a Graphics Ports drawing function (for example draw-line). You generate such a drawing-object by using any of the lw-gt:make-draw-*
functions, for example make-draw-line. You can also have a drawing-object that calls an arbitrary function by using make-a-drawing-call.
The non-leaf nodes in the hierarchy are made by instances of compound-drawing-object. compound-drawing-object has a sub-object slot, which contains a "drawing-object-spec" (either a list of "drawing-object-specs" or a drawing-object). Since the elements in lists are themselves "drawing-object-specs", that is can also be lists, part of the hierarchy can be done in lists of lists.
The main function of compound-drawing-object is to define the geometry of the drawing. The actual objects are instances of geometry-drawing-object which is a subclass of compound-drawing-object. These objects define the geometry, by rebinding the Graphics Ports transform, and then drawing their sub-object in this context. The width and height of the compound-drawing-object are also passed down, so geometry-drawing-objects inside the sub-object can use it when computing their own geometry.
You create a geometry-drawing-object by using one of:
Defines the rectangle for drawing the sub-object .
Scales its sub-object .
Rotates its sub-object .
make-absolute-drawing* and make-absolute-drawing
Draw their sub-object in the translated position, but without scaling or rotation.
Lists just draw their elements in the same geometry as their "parent".
To actually be drawn, the root of the hierarchy must be stored in the drawing-object slot of an "objects displayer", which is either an objects-displayer (subclass of pinboard-layout), or pinboard-objects-displayer (subclass of pinboard-object). The objects-displayer or pinboard-objects-displayer displays the hierarchy starting from the object in their drawing-object slot, passing its own geometry. The object in the drawing-object slot will typically be a list (which then draws its elements) or a compound-drawing-object (which then draws its sub-object with modified geometry). This process recurses and draws the entire hierarchy.
By default, both objects-displayer and pinboard-objects-displayer use an internal metafile as a way to cache the drawing and also to improve resizing.
drawing-objects do not have a permanent notion of "parent", and can appear concurrently as "children" of many "parents", and the same applies to a list in the hierarchy. The objects do not have any specific thread information and drawing does not modify anything in the objects. Therefore "drawing-object-specs" can appear concurrently in many places, whether inside the same hierarchy or in different hierarchies.
For example, the following do-object
function takes an object, and positions it at the bottom (with no positioning), middle and top. It then groups these three occurrences in a list ("drawing-object-spec"). It then uses "drawing-object-spec" twice, once inside pinboard-objects-displayer, and once in an objects-displayer that also displays the pinboard-objects-displayer. Thus the object is displayed six times: bottom, middle and top of the pinboard-objects-displayer, and bottom, middle and top of objects-displayer.
(defun do-object (the-object height)
(let* ((bottom-one the-object)
(middle-one
(lw-gt:position-object the-object
:bottom-ratio 0.5
:bottom-margin (/ height -2)))
(top-one
(lw-gt:position-object the-object
:bottom-ratio 1
:bottom-margin (- height)))
(drawing-object-spec
(list bottom-one middle-one top-one))
(pinboard-object
(lw-gt:make-pinboard-objects-displayer
drawing-object-spec
:x 80
:y 40
:width 100
:height 200 )))
(capi:contain
(make-instance 'lw-gt:objects-displayer
:description (list pinboard-object)
:drawing-object drawing-object-spec))))
We then use do-object
to display a red rectangle:
(do-object
(lw-gt:make-draw-rectangle 0 0 40 20 :filled t :foreground :red)
20)
You see that there are six rectangles. When you resize the pane, the three rectangles on the left, which are the rectangles in the drawing-object slot of the objects-displayer, resize too. That is because the metafile of the objects-displayer resizes. The three rectangles of the pinboard-objects-displayer do not resize, because the pinboard-objects-displayer does not change its size.
The function can be used for more complex objects:
(do-object
(list
(lw-gt:make-draw-rectangle 0 0 40 20
:filled t :foreground :red)
(lw-gt:make-draw-ellipse 20 10 20 10
:filled t :foreground :blue)
(lw-gt:make-draw-line 0 10 40 10
:filled t :foreground :green))
20)
The next example uses rotate-object. This first shifts the object to the right and down by using position-object, rotates the objects six times, rotating pi/3 each time, around a point which is in the middle of the height of the object, and distance of height to its left. Note that consequently the actual position of the copies is quite different from where position-object put them, which is a slightly counter-intuitive feature of rotate-object when using a rotating point which is not the center of the object:
(defun do-rotating (the-object height)
(let ((shifted
(lw-gt:position-object the-object
:left-margin height
:bottom-margin (- (/ height 2)))))
(let* ((rotated-copies
(loop repeat 6
for angle from 0 by (/ pi 3)
collect (lw-gt:rotate-object shifted angle)))
;; position the result in the middle of the pane
(positioned-drawing
(lw-gt:position-object rotated-copies
:bottom-ratio 0.5
:left-ratio 0.5)))
(capi:contain
(make-instance 'lw-gt:objects-displayer
:drawing-object positioned-drawing)))))
and rotate the same object that we used above:
(do-rotating
(list (lw-gt:make-draw-rectangle 0 0 40 20
:filled t :foreground :red)
(lw-gt:make-draw-ellipse 20 10 20 10
:filled t :foreground :blue)
(lw-gt:make-draw-line 0 10 40 10
:filled t :foreground :green))
20)
A sub-hierarchy inside a hierarchy can be modified destructively by setting the
sub-object
slot of compound-drawing-objects in the hierarchy. For example, we use the function do-object
above to display rectangles, and then make it switch between rectangles and ellipses:
(let ((rect
(lw-gt:make-draw-rectangle 0 0 40 20
:filled t :foreground :red))
(ellipse
(lw-gt:make-draw-ellipse 20 10 20 10
:filled t :foreground :blue)))
(let ((my-object
;; Use lw-gt:position-object to create a
;; compound-drawing-object, without actual positioning
(lw-gt:position-object rect)))
(let ((the-pane (do-object my-object 20)))
(dotimes (x 20)
(sleep 0.5)
;; modify the hierarchy
(setf (lw-gt:compound-drawing-object-sub-object my-object)
(if (evenp x) ellipse rect))
;; make it redraw
(lw-gt:force-objects-redraw the-pane)))))
In principle you can also modify the hierarchy by setting the cl:car
of a cons in a list inside the hierarchy, though that will make your code less clear. Do not set the cl:cdr
of conses in these lists.
As the example above shows, you do not need to do modifications in the pane thread (in contrast to operations on CAPI objects). If you modify the hierarchy while it is being drawn, the drawing in this drawing operation may be mixed up. However, normally you will want to force it to redraw using force-objects-redraw, which will draw correctly.
To make it easier to modify objects in the hierarchy, the functions that generate compound-drawing-objects all take keyword arguments data and function , which then are used to update the object automatically by calls to compute-drawing-object-from-data or recurse-compute-drawing-object. For example, the switch example above can be written using this mechanism, without having to remember my-object :
(defun my-updating-function (data)
(car data))
(let ((data (list nil)))
(let ((rect
(lw-gt:make-draw-rectangle 0 0 40 20 :filled t :foreground :red))
(ellipse
(lw-gt:make-draw-ellipse 20 10 20 10 :filled t :foreground :blue)))
(let ((my-object
;; Use position-object to create a compound-drawing-object,
;; without actual positioning, but with updating information
(lw-gt:position-object rect
:function 'my-updating-function
:data data)))
(let ((the-pane (do-object my-object 20)))
(dotimes (x 20)
(sleep 0.5)
(setf (car data) (if (evenp x) ellipse rect))
(lw-gt:recurse-compute-drawing-object the-pane))))))
Because drawing-objects do not actually know which hierarchy they are in, they cannot tell their containing pane to redraw. We used force-objects-redraw in the first example above, and in the last example above we rely the fact that recurse-compute-drawing-object, when called on a pane, does this itself. In general, to actually get the pane redrawn, you will have to have a call of some function (force-objects-redraw or a function that calls it) on either the pane or on a pinboard-objects-displayer.
Note that just invalidating the pane (by invalidate-rectangle) does not cause redrawing of the drawing-objects when a metafile is used (the default case). That is intentional, to make exposure and resize fast.
Modifying the hierarchy is thread-safe, in that threads modifying the hierarchy in parallel, and even parallel to it being drawn, will not cause a problem on its own. However there is no guard against different threads making conflicting changes. For example, if thread A sets the sub-object of a compound-drawing-object, and at the same time thread B sets something inside the sub-object , then the change that thread B made will not be visible in the hierarchy. You will have to guard against such conflicts.
The drawing-object code cannot cope with a circular hierarchy.
CAPI User Guide and Reference Manual (Windows version) - 3 Aug 2017