source files: [xc]cmr.[ch]
(SEND <cmr> :RUN-WIDGETS [ :DOWNCLICK-HOOK <hook-fn(s)> ] [ :DRAG-HOOK <hook-fn(s)> ] [ :UPCLICK-HOOK <hook-fn(s)> ] [ :SELECT-HOOK <hook-fn(s)> ] [ :DESELECT-HOOK <hook-fn(s)> ] [ :RESELECT-HOOK <hook-fn(s)> ] [ :<keyword> <value> ] [ :THINGS <thinglist> ] )
This is the principal message used to implement user interaction via the mouse. The :THINGS <thinglist> should normally be the list of things just drawn via a :DRAW command, otherwise the results will be probably be nonsensical. If the :THINGS option is not present, the <thinglist> should be present as an :THINGS property on the <cmr> or, failing that, as an :THINGS property on the MATRIX44 instance associated with the <cmr>.
The basic idea is that :RUN-WIDGETS finds the thing in view (if any) to which the mouse is pointing, and then calls the appropriate HOOK function associated with that thing, if any. That is, if the user just downclicked on a thing, and the thing has (implicitly or explicitly) a :DOWNCLICK-HOOK specified, then :RUN-WIDGETS will run that function/functionlist.
This provides a very simple yet flexible mechanism on which widget functionality may be built -- once the code to draw a thing has been written, the thing can be made mouse-sensitive with just another line or two of code.
The :RUN-WIDGETS message implictly takes three arguments from the global symbols XG.3D-MOUSE-ROW, XG.3D-MOUSE-COL and XG.3D-MOUSE-STATE. These will normally have been set by sending the :READ-MOUSE-STATE message to (any) camera, but may also be set by hand if non-mouse control is desired. In any event, XG.3D-MOUSE-ROW/COL should contain the pixel location of the mouse within our window (origin 0,0 at lower-left), and XG.3D-MOUSE-STATE should be one of :UPCLICK, :DOWNCLICK, :DRAG, :SELECT, or NIL.
If XG.3D-MOUSE-STATE is NIL the :RUN-WIDGETS call will be a NO-OP. (Note that it is normal to call :RUN-WIDGETS on every active camera, once per frame.)
Return value is T if a hook was run, else NIL.
A 'widget,' for purposes of the :RUN-WIDGETS call, is any thing (in the <thinglist> sense, see :DRAW for details) which either has a :DOWNCLICK-HOOK, :DRAG-HOOK, :UPCLICK-HOOK or :[DE|RE]SELECT-HOOK value explicitly specified, or which inherits one of these from the global :RUN-WIDGETS options list. (Thus, if any of the HOOK options are globally specified, all things in the <thinglist> become widgets.)
The <hook-fn(s)> argument may be either a function/closure of no arguments, or else a list of them. (They take no arguments partly for speed -- the functions can then use underhanded C techniques to obtain their arguments if they wish -- and partly in line with the uniform policy that *all* hook functions in Skandha4 take no arguments, to make it simple to use a given hook function anywhere it is sensible to do so.)
The :RUN-WIDGETS message sets the global variable XG.3D-CURRENT-CAMERA to <cmr> while it is running for the benefit of the hook functions, and then restores it to previous value when done. Thus, any hook function called may send messages to XG.3D-CURRENT-CAMERA to find out what it wishes to know about the current user selection. To support this, the following :GET properties are available from this camera (actually, any camera will give the same result) during the :RUN-WIDGETS message:
(The :RUN-WIDGETS call uses a number of XG.3D-* variables for internal bookkeeping. These are definitely not intended to be written by user code, and even reading them is unwise, since they may be changed or eliminated in future versions of the system. Hence, we do not document them here. Fools and gurus may dig them out of xcmr.[ch].)
The :RUN-WIDGETS message works by mathematically solving for the intersection (of the line through space extending out from the mouse cursor location) with a polygon on some thing. (Since lines and points have no area, the chance of actually intersecting one of them is essentially zero, and this is not checked at all -- it is currently not possible to pick lines or points. If you need to do so, you should probably draw them as small boxes, cylinders, or whatever. A possible future extension is a :RADIUS keyword specifying the 'effective' size of points considered as spheres and of line segments considered as cylinders, for picking purposes.) The intersection problem is quite similar to that used by ray-tracing programs, and in fact was adapted from raytracing code.
While :RUN-WIDGETS does not use the normal workstation rendering code at all, it *does* correctly account for clipping, ensuring that only visible polygons will be selected. (Currently there are a few bugs in this rendering emulation. For example, backfaces are hit-tested even if :DROP-BACKFACES is set non-NIL. These will be fixed by and by...)
The :RUN-WIDGETS message will call the appropriate :DOWNCLICK-HOOKS function(s) if the user just downclicked, the appropriate :UPCLICK-HOOKS function(s) if the user just released the mouse button, and the appropriate :DRAG-HOOK function(s) in the interval between. If the mouse button is up, and if the thing which was under the mouse cursor last frame is no longer under it and had a :DESELECT-HOOK specified, that hook will be called. This is typically used to de-highlight an thing. If the mouse button is up, and the thing under the mouse cursor was not under it last frame and has a :RESELECT-HOOK specified, that hook will be called. This is typically used to highlight an thing. If the mouse button is up and the thing under the mouse cursor has a :SELECT-HOOK specified, that hook will be called.
One normally calls :RUN-WIDGETS once per viewport/camera per frame (a 'frame' being defined as the interval between two :NEXT-FRAME messages), but there is no harm in calling :RUN-WIDGETS more frequently than this, and in fact if the user is clicking faster than once per frame, it may make sense to process all pending clicks before redrawing.
It is not an error for a downclick not to select any thing: no-thing happens. (Note that, if desired, the application programmer may trap misses by specifying a :DOWNCLICK-HOOK on a :PICK-AS :BACKGROUND thing in the thinglist, and in fact may trap *all* mouse activity within a given viewport by specifying hooks on a :PICK-AS :FOREGROUND thing within the thinglist. The latter may be useful in, for example, a 'paint' or 'draw' application operating on pixel images.)
If a downclick *does* select a thing, then the selected viewport, thing and polygon remain selected as long as the mouse button is held down, even if the mouse is moved off the polygon or thing, or even entirely outside the viewport. Further, the viewport and parametric coordinates returned will always be within the appropriate range.(The rationale here is to make the widget code simple and robust.)
NOTE: If clicking on a thing is being used to delete the thing, or facets within the thing, it is usually best to have the deletion triggered by :UPCLICK rather than :DOWNCLICK or :DRAG, to prevent either internal :RUN-WIDGETS code, or else application hook functions, from being puzzled by a dragged thing or facet vanishing midway. To encourage this, :RUN-WIDGETS will signal an error if it detects a such vanishing facet. By 'deleting' we mean actually resizing the relation, not just setting visibility bits.
NOTE: Picking takes time proportional to the number of polygons in the scene, in general. This means that in a camera viewport displaying a complex scene, picking may consume considerable compute resources. In such viewports, you may wish to avoid using :[DE|RE|]SELECT-HOOK, since they can require a full pick computation to be done every frame. By contrast, :UPCLICK-HOOK and :DOWNCLICK-HOOK hook do picks only on the frame in which the user actually moves the mouse button, and :DRAG-HOOK doesn't do a full pick computation, only a cheap intersection test on the particular polygon being dragged. Please note that pick time is not affected by scenes displayed by *other* cameras! Also that it has to check all polygons in the displayed thinglist, in general a scene may look very simple if most polygons are obscured, but still take a long time to pick-process.