by Richard S. Sutton
This document describes G, a set of routines for low-level graphics programming. G provides a standard language for graphics programming that is easily portable across machines, languages, and graphics devices. The design of G is based on Gus, a graphics package designed by Andrew Cromarty, Richard Sutton and others at the University of Massachusetts in 1981, and on the window system in Macintosh Common Lisp (MCL). G is meant to support the simultaneous use of multiple graphical devices. This document describes the G routines and their use in MCL.
G is not intended to be complete or static. New functionality can easily be added to it. For example, the current specification does not include the ability to draw ovals, but it would be easy to add this, and the current design suggests all the arguments and names for the new routines that would be needed.
This document includes an HTML anchor for every G routine, to facilitate automated access to their documentation. Also, access to the source code of all routines and lisp objects is available by clicking on the bracketed descriptor to the far right of each routine or object.
All graphical operations in G are done in the context of a special graphical environment called a view. The view specifies the region of the display to draw into, the coordinate system for drawing, and a variety of other state information pertaining to drawing commands. Typically, a user will have several views open at the same time, each supporting a different way of drawing onto a device. Windows are a special kind of view, and there is one view corresponding to the entire surface of the graphics device (e.g., to the whole screen).
The region of the display corresponding to a view is called the view's viewport. G provides a complete set of viewport routines for altering and examining viewports.
Views support drawing within their viewports in two coordinate systems. The normalized coordinate system permits drawing in floating-point coordinates within a range of x and y coordinates specified by the user on a view-by-view basis. Many users will probably operate exclusively with normalized coordinates, but those who wish greater control and efficiency can use the device coordinate system. The device coordinate system uses integer coordinates corresponding to the actual pixels of the graphics device. Use of the two coordinate systems can be freely intermixed, even for the same view.
If one draws over the full range of a view's coordinate system then the result will also cover the full span of the view's viewport. Attempts to draw outside the coordinate system will be "clipped" at the viewport boundary and will not appear on the display.
G provides a complete set of coordinate system routines for setting the normalized coordinate system and for examining the current status of both the normalized and the device coordinate systems.
It is convenient to organize views into hierarchies. The viewports of child views are in terms of the coordinate systems of the the parent views, and are automatically moved when their parent is moved. In the primary example of this, the parent view is a window and the child views are subregions of the window. It is much easier to specify the viewports of the child views with respect to the window than, say, with respect to the screen as a whole. When the window is moved, the child views naturally move with it.
The viewports of child views are not restricted to lie within the viewports of the parent view, but any portion protruding beyond the parent view will not be visible on the display (it will be clipped).
In G, there is one hierarchy of views for each display device. The root view of each hierarchy is the device itself.
G treats the window system in MCL as a display device called *g-device*. This device is the root view and the
windows are the first generation child views. Drawing is not permitted
directly on the device, but must be through windows. MCL has its own object
classes called view
and window
, which are the basis
for, but distinct from, G object classes g-view
and g-window
(see figure).
Much of G can be used without learning about its special object classes and routines for setting up views, but using the standard MCL objects and routines instead. In this case drawing can only be done in device coordinates, but it will still be simpler than using the MCL Quickdraw interface. If g-views and g-windows are simply substituted for MCL's views and windows, then drawing will also be much more efficient than using the MCL Quickdraw interface.
Alternatively, G can be used without learning about MCL's special object classes and routines for setting up views, but using entirely G routines instead. To a large extent one can use G routines instead of MCL routines for all programming operations on windows.
All G routines begin with the prefix g-
or gd-
.
The gd-
prefix is used for routines that specify coordinates in
device coordinates and the g-
is used otherwise. A suffix
convention is used for routines that specify two endpoints, such as
g-draw-line
. One version of the routine specifies both endpoints
in absolute coordinates, while another specifies the second endpoint
relatively, by an offset from the first. The relative version of the routine has
a suffix of "-r
". For example, the version of
g-draw-line
that specifies the second endpoint relative to the
first is named "g-draw-line-r
". This convention is used
throughout G, not just for drawing routines.
Routines for specifying and constructing colors to be used for drawing all
begin with g-color-
. All other G routines are meant to read as
imperative commands, i.e., with a verb and object. For example,
g-set-viewport
is the routine directing G to set the viewport of
a specified view, and g-draw-circle
is the command directing G to
draw a circle. This may at first seem a little verbose, but it wears well. All
G names are defined in a separate package called "g:". All routines that work by
side-effect include the word "set
" in their names. Wherever the
phrase "coordinate-system
" appears in a routine name it can be
abbreviated "cs
".
(load "g.lisp")
(use-package :g)
(setq my-window (make-instance 'g-window))
(setq black (g-color-black my-window))
(g-draw-circle my-window .5 .5 .1 black)
Here are the major class-subclass relationships in G:
The basic class of all G views. Inherits from MCL's view
class. Mix this into new classes to define your own specialized G views.
(make-instance 'g-view :parent parent)
returns a new G view with parent parent, which must be an existing window or view. If parent is not provided, then it
defaults to the frontmost g-window. If there are no g-windows, then it
defaults to the frontmost window of any class, typically the listener being
typed into. Other initargs accepted by MCL's view
class can also
be provided.
The class of all G windows. Inherits from g-view
and MCL's window
class. Mix this into new classes to define your own specialized G windows.
Windows are a special class of view in that they appear with a border around them, including a title-bar at the top. The viewport of a window refers to the region inside the border. If you want the border to be visible you must leave extra room for it.
make-instance 'g-window &key :parent :g-viewport :gd-viewport :g-viewport-r :gd-viewport-rReturns a new G window on the device given by parent. If no parent is specified
it defaults to *g-device*
. If one of the
viewport arguments is provided it is a list of four numbers, (x1 y1 x2
y2) or (x y delta-x delta-y), specifying the initial viewport of
the new window as if they had been input to one of the viewport setting routines. At most one viewport argument should be
provided. Other initargs accepted by MCL's window
class can also be
provided. For example:
(setq my-window (make-instance 'g-window
:g-viewport '(.5 .5 .75 .75)
:window-title "My Window"))
The class of all G graphics devices. Inherits from g-view
.
This variable is bound to the root view and the graphics device
corresponding to the main screen, an instance of g-device
.
(Drawing is not permitted directly on *g-device*
.) All windows
that have no explicit parent are taken to have *g-device*
as
their parent. Thus, it is the parent view of all regular MCL windows. It
can be passed into coordinate system
routines that use normalized coordinates to establish a new coordinate
system for positioning windows on the screen. For example, here is how one
would establish a coordinate system for positioning windows in inches from
the upper left corner of the screen:
(g-set-cs-scale *g-device* 0 0 :inches :inches :upper-left)
In MCL, the viewport of *g-device*
is always the entire
screen, but the first 20 pixels of this are usually obscured by the menubar.
Another 18 pixels are needed for window titles. Thus, if you want to make sure
a window placed to start at 0,0 in normalized coordinates has its border and
title bar within the visible range, you might do:
(g-set-cs-scale *g-device* (/ -1 72) (/ -38 72) :inches :inches :upper-left)
Multiple screens? Other devices? Yes, yes, but not yet!
(g-get-parent view) returns the parent of
view. This value is suitable for passing into
make-instance
to make a sibling view.
(g-get-children view) returns a list of the child views of view.
When you are done using a G view you should close it, causing various bookkeeping operations to be done. In MCL, the view's parent disposes of the subview corresponding to the view. If view is it window, it will disappear from the screen.
G provides a complete set of routines for examining the normalized and device coordinate systems of a view, for setting the normalized coordinate system, and for converting back and forth between device and normalized coordinate systems.
Coordinate systems are specified by two points, together giving the
extreme x and y coordinates that will appear within
the viewport, plus an indication of the corner of the viewport that will be
occupied by the first point. For example, (g-set-coordinate-system
view 0 0 1 1 :lower-left)
sets the coordinate system for
view to run from 0,0 in the lower-left corner of the viewport to
1,1 in the upper right. The allowed values for the corner, in all routines, are
:lower-left, :upper-left, :lower-right
, and
:upper-right
.
The second point that defines a coordinate system can be specified
either absolutely or relatively (as an offset with respect to the first
point). In the relative case the routine ends with "-r
".
Routines starting with "gd-
" pertain to (or return coordinates
in) the device coordinate system whereas routines starting with
"g-
" pertain to (or return coordinates in) the normalized
coordinate system. Thus, the routine g-set-coordinate-system-r
is used to set the normalized coordinate system of a view, specifying its
second corner point relative to the first. In all coordinate system
routines, the phrase "coordinate-system
" can be abbreviated to
just "cs
".
Returns five values, x1, y1, x2, y2, corner, describing the
current normalized or device coordinate system for view. The
coordinate system runs from coordinates x1,y1 to
x2,y2, with coordinates x1,y1 occupying the corner of
the viewport indicated by corner, which is one of
:lower-left, :upper-right, :lower-right
, or
:upper-left
. It will always be the case that x1 <
x2 and y1 < y2. g-get-coordinate-system
requires that view be a g-view.
Returns five values, x, y, x-size, y-size, corner, describing
the current normalized or device coordinate system for view. The
coordinate system runs from coordinates x,y to
x+x-size,y+y-size, with coordinates x,y occupying the
corner of the viewport indicated by corner, which is one of
:lower-left, :upper-right, :lower-right
, or
:upper-left
. x-size and y-size will
always be positive. g-get-coordinate-system-r
requires that view be a g-view.
Returns five values, x, y, x-scale, y-scale corner,
describing the current normalized coordinate system for view. The
coordinate system gives the corner of the viewport indicated by
corner the coordinates x,y and then increases across
the rest of the view according to the scale arguments x-scale
and y-scale. corner is one of :lower-left, :upper-right,
:lower-right
, or :upper-left
. The scale parameters are
positive floating point numbers giving the scaling in pixels per unit of
the normalized coordinate system.
(g-set-coordinate-system view x1 y1 x2 y2 corner) sets the
normalized coordinate system for view to run from coordinates
x1,y1 to coordinates x2,y2, with the point
x1,y1 occupying the corner of the viewport indicated by
corner, which should be one of :lower-left, :upper-right,
:lower-right
, or :upper-left
. In subsequent calls to
g-
drawing routines using view, the normalization will
be performed such that objects drawn to fill the these new coordinates will
fill the viewport of the view. (gd-
coordinate specification
is not affected.)
The initial normalized coordinate system for each view is from 0,0 in the
lower-left corner to 1,1 in the upper right. This could be restored at any time
by (g-set-coordinate-system view 0 0 1 1 :lower-left)
.
(g-set-coordinate-system-r view x y delta-x delta-y corner)
sets the normalized coordinate system for view to run from
coordinates x,y to coordinates x+delta-x,y+delta-y,
with the point x,y occupying the corner of the viewport
indicated by corner, which should be one of :lower-left,
:upper-right, :lower-right
, or :upper-left
. Otherwise,
the efect of this routine is the same as discussed above for
g-set-coordinate-system
.
(g-set-cs-scale view x y x-scale y-scale corner)
sets the normalized coordinate system of view to have
coordinates x,y in the corner of the viewport indicated by
corner, and then increase across the the rest of the view
according to the scale arguments x-scale and
y-scale. corner should be one of should be one of
:lower-left, :upper-right, :lower-right
, or
:upper-left
. The scale parameters should be one of
:inches, :centimeters, :pixels, :points
, or a positive
number indicating pixels per unit of the normalized coordinate system.
See *g-device* for an
example of how to set use g-set-cs-scale
to set the normalized
coordinate system such that windows can henceforth be positioned in inches
from the upper left corner of the screen.
Returns the normalized coordinate corresponding to the provided device coordinate, with respect to view.
Returns the device coordinate corresponding to the provided normalized coordinate, with respect to view.
Converts an offset (distance) in device coordinates to the same offset in normalized coordinates, with respect to view. Note that although the arguments are scaled in pixels, they may be reals to prevent round off error.
Converts an offset (distance) in normalized coordinates to the same offset in device coordinates, with respect to view. Note that although the returned values are scaled in pixels, they will be reals to prevent round off error.
Given an x or y coordinate in the coordinate system of from-view, these routines return the corresponding coordinate in the coordinate system of to-view.
G provides routines for setting and examining the viewport of each view in both normalized and device coordinates. A view's viewport is always given in terms of a coordinate system of the parent of the view.
Changing the viewport affects subsequent drawing operations. In
subsequent drawing, clipping will be to the new viewport (or rather to the
portion of it which is viewable and within the parent view's viewport). In
addition, in subsequent calls to g-
graphics routines,
normalization of the coordinates will also be to the new viewport. Thus, a
program that normally fills the entire parent view can be made to use just
a portion of it merely by resetting the viewport (provided it uses entirely
normalized coordinates). For example, if the parent has the default
coordinate system (running from 0 to 1 in both dimensions), then one can
use just the upper right quandrant of it by calling (g-set-viewport
view .5 .5 1 1)
.
Changing the viewport normally does not change the coordinate systems in any way. However, it is possible to trigger arbitrary used code whenever the viewport is changed. See the section on Hooks into Viewport Setting.
I'd prefer that changing the viewport did not change the display in any
way. However, in the current implementation, using MCL views, this does not
seem to be possible. Whenever the viewport is changed, any regions
uncovered or newly covered will be erased, and then have g-draw-view
called on them).
Returns four values, x1, y1, x2, y2, indicating the current
viewport in device coordinates or normalized coordinates, in the coordinate
system of the parent of view. It will always be the case that
x1 < x2 and y1 < y2.
g-get-viewport
requires that the parent of view be
a g-view
.
Returns four values, x y x-size y-size, indicating the current
viewport in device coordinates or normalized coordinates, in the coordinate
system of the parent of view. x and y are
the position of the corner of the viewport that has smallest coordinates,
and x-size and y-size are the size of the viewport.
g-get-viewport-r
requires that the parent of view
be a g-view
.
Sets the viewport for view to the rectangular portion of the display
including the pixels x1,y1 and x2,y2, in the normalized or
device coordinates of the parent of view. g-set-viewport
requires that the parent of view be a g-view
.
The default viewport after initialization of a view is the full display surface of the parent.
Sets the viewport for view to the rectangular portion of the
display including the pixels x,y and x+delta-x,y+delta-y, in
the normalized or device coordinates of the parent of view. If any
of the last four arguments are nil (or not provided) then they default to
their values for the current viewport. g-set-viewport-r
requires that the parent of view be a g-view
.
This method is called whenever the viewport of view is
changed in size. The default method does nothing. Users may want to write
specialized methods for specialized view or window classes in order to make
automatic adjustments whenever the viewport size is changed. For example,
instead of having the normalized coordinate system expanding to fill a
larger viewport, one may prefer the coordinate system to grow
proportionately, to reveal more of the data space. This and other affects
can be easily achieved by resetting the coordinate system each time the
viewport is changed, e.g., by calls to g-set-cs-scale
. For
example, the effect just described can be accomplished as follows:
(defclass fixed-scale-window (g-window) ())
(defmethod g-accept-new-viewport-size :before ((window fixed-scale-window))
(g-set-cs-scale window 0 0 :inches :inches :upper-left))
Now window classes with fixed-scale-window
mixed in will behave as desired.
Consider another example of the use of
g-accept-new-viewport-size
. Suppose you want the sizes and
positions of child views within a window to be relative to the viewport of
the window. The natural way to do this is to set up a normalized coordinate
system for the window and place the child views within the window using
that coordinate system. However, ordinarily all this would get messed up
if the window's viewport were changed, because the viewports of the child
views would not be readjusted The following code defines a mixin that
causes the child views to all be automatically changed as well:
(defclass maintain-g-viewports-of-children (g-view) () )
(defmethod g-accept-new-viewport-size ((view maintain-g-viewports-of-children))
(let ((children (g-get-children view))
(g-viewports (loop for child in children
collect (multiple-value-list
(g-get-viewport child)))))
(call-next-method view)
(loop for child in children
for g-viewport in g-viewports
do (apply #'g-set-viewport child g-viewport))))
Views of this class, or with this class mixed in, will, whenever their viewports are changed, automatically change the viewports of their child views to maintain their positions and sizes in the normalized coordinates of this view. Thus, whenever a view is changed in size, all the child views will be changed proportionately.
G provides several routines for specifying colors, all
beginning with g-color-
. Each returns a
color-code to be used in drawing routines. Color-codes
may be specific to the window, and thus a view must be
specified whenever constructing a color-code. Normally,
color-codes are constructed infrequently and then used over
and over, but it is also possible to construct a new
color-code each time anything is drawn.
If the device does not support the requested color -- for example, if you ask for a blue color when using a gray-scale screen -- then you will get some approximation to the requested color.
In MCL, one can also set the characteristics of the Quickdraw "pen" as part of the color-code. The pen specifies various characteristics of Quickdraw drawing, which underlies G. These characteristics can be set by the G routine g-color-pen, but these effects are entirely device specific.
Color-codes should not be altered by the user in anyway. The implementation may rely on this.
All these functions return a color code for view
according to the last part of their name. The name "flip" refers
to a color that reversibly inverts the color currently on the screen.
The color "invisible" draws but does not affect the color of any pixels
on the screen. (These and other possibilities can also be constructed
via the use of the MCL specific routine g-color-pen
.) The names off
and on
have special meaning in conjunction with the g-clear
drawing command.
g-clear
sets the entire display to the
off
color, which may be different for different
devices. In general, on
is the normal drawing
color for a device, and off
is its opposite, or
erasing color.
For MCL windows,
on
is the same as :black
and
off
is the same as :white
.
Allows the user to pick the desired color using the standard interactive Macintosh color picker. Initargs can be used to set the initial color and other aspects of the color picker.
Returns a color-code for view corresponding to the specified red, green, and blue color components, which range from 0 for minimum intensity to 1 for maximum intensity.
Returns a color-code for view corresponding to the specified red, green, and blue color components, which range from 0 for minimum intensity to 255 for maximum intensity.
Returns a color-code for view for the color specified according
to its intensity along a continuum from the off
color
at intensity=0 to the on
color at intensity=1.0.
See above for definitions of on
and off
colors.
Returns a new color-code for view that is like color-color except that it has quickdraw pen characteristics as specified by the remaining arguments. If an argument is not provided, then that characteristic of the pen is unchanged.
The pattern is an 8 by 8 bit pattern that acts like the ink of the pen. MCL provides five patterns as constants:
:white-pattern
:gray-pattern
will have a checker-board dithering effect in which only every alternate pixel is affected. The default pattern in :black-pattern
, which changes all the pixels.
The mode determines the interaction between the pixels being drawn (from pattern) and the pixels already on the display. For example, the drawn pixels could replace them, OR with them, XOR with them, etc. There are eight modes:
:patCopy Replace (paint over) existing pixels (the default)The last two arguments to
:patOr Or with the existing pixels (black if either black)
:patXor Wherever source is black, invert the existing pixels
:pacBic Wherever source is black, erase the existing pixels
:notPatCopy like :patCopy, but first invert source pixels
:notPatOr like :patOr, but first invert source pixels
:notPatXor like :patXor, but first invert source pixels
:notPatBic like :patBic, but first invert source pixels
g-color-pen
make the
pen x-size pixels wide and y-size pixels
tall. This causes lines and other graphical objects drawn with
the new color to appear with the new thickness. When the pen
size is greater than one, the extra pixels are drawn below and
to the right of the usual pixels.
The drawing routines are those G routines that perform a display (or output-buffering) function.
Most drawing routines come in two forms, one prefaced by
g-,
and the other by gd-
. These two forms
differ only in the method used to specify spatial coordinates on the
display surface. The g-
routines use normalized
real-valued coordinates. Many users will probably use exclusively
g-
routines, but for those who wish to have greater
control at the pixel level, the gd-
routines operate in
device-dependent integer coordinates corresponding to pixels of the
display. Most calls to g-
routines get quickly
translated into calls to gd-
routines within G. The use
of these two kinds of routines can be freely intermixed.
A suffix
convention is used for routines that specify two endpoints, such as
g-draw-line
. One version of the routine specifies both endpoints
in absolute coordinates, while another specifies the second endpoint
relatively, by an offset from the first. The relative version of the routine has
a suffix of "-r
". For example, the version of
g-draw-line
that specifies the second endpoint relative to the
first is named "g-draw-line-r
".
Here are some examples of drawing in device coordinates:
The arguments of all the drawing routines are patterned as follows.
The first argument is the view within which drawing takes place. The
following arguments typically specify the x and
y coordinates of the drawing operation. For g-
routines these are in normalized coordinates. For gd-
routines, these are in pixel coordinates, and in MCL they must be
integers. The final argument is an optional color-code. (Color codes
are constructed by the color
specification routines.) If a color is not provided (or nil), then
the color used is the same as the last color used with the window associated with the view.
To establish a current color for drawing without calling an actual
drawing routine, use g-set-color
.
Not all of Quickdraw's abilities are currently available via G routines. Additional routines should be added, patterned after these, as the need for their additional abilities arise.
Clears the viewport by filling it with color, or with
the off
color if
color is not provided. Under normal circumstances, a
display is initialized to the off
color, but G itself
performs no initialization of display.
Makes the effects of all previously called graphics routines visible in the display for the device associated with view. For MCL, this causes the window corresponding to view to be made frontmost.
Colors the pixel x,y on view.
Draws the line segment between and including the pixels x1,y1 and x2,y2.
Draws the line segment between and including the pixels x,y and x+delta-x,y+delta-y.
Draws a wire-frame or hollow rectangle with one corner at x1,y1 and the other at x2,y2.
Draws a wire-frame or hollow rectangle with one corner at x,y and the other at x+delta-x,y+delta-y.
Only vertically or horizontally oriented rectangles can be drawn -- arbitrary angles are not possible. A box of dimension zero appears as a single pixel.
Fills in the rectangular area including the pixels x1,y1 and x2,y2 with color.
Fills in the rectangular area including the pixels x,y and x+delta-x,y+delta-y with color.
Only vertically or horizontally oriented rectangles can be drawn -- arbitrary angles are not possible. A rectangle of dimension zero appears as a single pixel.
Fills in the polygonal area specified by the points x1,y1 x2,y2 x3,y3 ... with color. The area filled is the area inside the points, such that if they are all the same, then nothing is drawn. If color is nil, then it defaults to the last drawn color on the view. There must be at least one point.
Draws a circle to the display with its center at x,y and with radius
radius. A circle of radius zero will not appear on the display at all. A
radius passed to g-draw-circle
is interpretted as a distance along the
"y" or vertical dimension and the circle size is scaled accordingly .
As currently implemented, this routine will draw circles that are
as wide as they are tall as measured in numbers of pixels.
Thus, if the aspect ratio of the display is not 1:1, circles will
come out as elipses. The shape of the circle is never affected by the
coordinate systems associated with view. Note that this
can cause different parts of a display to change their positions
relative to each other (when using g-
routines) as a
view is mapped to different viewports.
Draws a disk (i.e., a filled-in circle) to the display with its center at x,y and with radius radius. Otherwise, these routine behave just like the circle-drawing routines above.
Draws a arc to the display with its center of rotation at x,y
and with radius radius. The arc begins at
starting-angle degrees and proceeds for angle
degrees. Degrees follow the standard mathematical conventions, increasing
counterclockwise from zero pointing directly to the right. For example,
(g-draw-arc view x y radius 180 90
color)
draws a quarter circle in the lower left quadrant.
Otherwise, these routines behave similarly to the circle-drawing routines
described above.
Writes the string of text to view in color color. The
lower left corner of the string appears at x,y. Font is a list of font description information, e.g.,
("Monaco" 12 :italic :bold)
in MCL.
In MCL, permitted font names
include all the fonts installed in your system. The font size is in points
from 1 to 127. The font styles should be one or more of the following:
:plain, :italic, :bold, :outline, :condense, :shadow, :extend,
:underline
. If several of these are provided, they are processed in
order. A :plain
font style implies the absence of other font
styles. Any aspects of the font not explicitly specified defaults to its value
in the last font used in the view. Thus to obtain an italic font that is
ensured not to be bolded (or extended, or whatever) one must use the sequence
of styles :plain :italic
.
Writes the string of text to view in color color. The center of the string appears at x,y. Font is as described above.
Returns the vertical size of text when drawn to view in font, in either pixels or normalized coordinates. Font is as described above.
Returns the horizontal size of text when drawn to view in font, in either pixels or normalized coordinates. Font is as described above.
(g-set-color view color)
establishes color as the
current drawing color of the window associated with view. Subsequent drawing to any
view within that window that does not
explicitly specify a color will appear in this color. This will remain in effect until
something is drawn into the window that does specify a color, or until the next call to g-set-color
.
These are routines for reading the cursor location, controlling its appearance, and responding to mouse events.
Returns as two values the current x and y coordinates of the cursor (mouse) in the coordinate system of view. If the cursor is not within the viewport of view, then nil is returned.
If you would like your program to be able to respond to mouse clicks
within a G view, then you should specialize the g-view class and write
a
new method for g-click-event-handler
or
gd-click-event-handler
specialized for the new class. That
method will
be called with the coordinates of the mouse click whenever there is a
mouse click within the viewport of a view of that new class. For
example, here is how to cause the current mouse coordinates to be
printed
to the listener each time the mouse button is clicked within the view:
(defclass my-view (g-view) ())
(defmethod g-click-event-handler ((view my-view) x y)
(print (list x y)))
Use these methods if you would like to have the cursor appear in some special form (possibly dependent of its coordinates, x and y) when it moves over the viewport of a G view. For example, to make the cross-hairs cursor appear whenever the cursor goes over your view, you would do the following:
(defclass my-view (g-view) ())
(defmethod g-cursor ((view my-view) x y)
g::*cross-hair-cursor*)
This routine is called each time your view is moved or uncovered
from behind other windows. If you make a specialized form for a
subclass of g-view, then you can arrange for the view to to be
automatically redrawn when uncovered. Otherwise, covered and uncovered
views are simply blanked out with the off
color. For
example, suppose you want a black circle on a white background to
always appear in your view. This is what you should do:
(defclass my-view (g-view) ())
(defmethod g-draw-view ((view my-view))
(g-draw-circle view .5 .5 .2 (g-color-black view)))
(defmethod g-accept-new-viewport-size :after ((view my-view))
(g-clear view)
(g-draw-view view)) ; this causes complete redrawing on viewport changes
A little care is needed if you use g-draw-view
for a
window and you want to initialize its colors at the time the window is
created. If you initialize the color-codes as slots in a standard
:after method to initialize-instance
, then an error will
occur when the window is first made. This is because the event system
tries to draw the window as soon as it is made, which is before the
:after method has been run to define the color-codes. The solution is
to hold off on event processing until your new window has been
completely initialized. This is done by writing a new primary method
for initialize-instance
that invokes without-event-processing
, as follows:
(defclass my-window (g-window my-view)
(black)) ; black will be a color-code
(defmethod initialize-instance ((window my-window) &key)
(without-event-processing ; Temporarily suspend event processing,
(call-next-method) ; then run main inits without events
(setf (slot-value w 'black) ; and make any color-codes.
(g-color-black window)))) ; The view will be draw when events resume.
The following routines follow the argument conventions established for G, and are designed to extend and be compatible with the other G routines. Since they are written "on top of" G, i.e., since they simply call G routines, they are not considered elementary G routines. (Obviously, this division is a bit arbitrary.)
Draws an arrow from x1,y1 to x2,y2 on view.
Draws an arrow from x,y to x+delta-x,y+delta-y on view.
Draws an arrow from x1,y1 to
x2,y2 with independent control over the size of
the main segment of the arrow and the head of the arrow.
Body-size is the fraction of the total length between the
two endpoints that is filled by the main segment (starting from the
head end of the arrow) and head-size is the relative
length of the head segments to the distance between the endpoints.
For example, just the arrowhead alone could be drawn by setting
body-size=0. As another example, g-draw-arrow
is
implemented as
(defun g-draw-arrow (view x1 y1 x2 y2 &optional color)
(g-draw-arrowhead view x1 y1 x2 y2 1 .25 color))
Draws an arrow from x,y to x+delta-x,y+delta with independent control over the size of the main segment of the arrow and the head of the arrow. Otherwise as above.
G as described herein can be obtained by downloading the file g.lisp.html and renaming it "g.lisp
". To install G, first load the MCL quickdraw interface, e.g., by (load "ccl:library;quickdraw")
(if necessary), and then load the G source file, e.g., by (load "g.lisp")
. If you then "use" the newly created package, e.g., by (use-package :g)
, you will have access to all the G routines.
To avoid continual refocusing, each routine checks when it runs
whether or not the view is focused properly. It is refocused only if
necessary. The check is by checking if MCL's
*current-view*
is the same as the view. Refocusing is
done by calling focus-view
. (This trick has been disabled
for the moment because of an incompletely understood interaction with MCL's dialog-view functions.)
A similar trick is done to prevent
unneeded calls to set-fore-color
. The last foreground color
set on each view is stored with the view. It is set again only if a new color is
asked for. This assumes color-codes are never changed, i.e., that they are used
only with G routines. It also assumes no other processes are changing the
foreground color of the views without changing it back when they are done!
These tricks result in an efficient graphics system. G is about 5 times
faster than calling quickdraw routines through the normal interface provided in MCL (this is based
on drawing random line segments within a 200 by 200 pixel window).
Compared to the fastest possible interface from MCL, undercutting the
quickdraw interface, G loses by only about 10%. All these comparisons
are for gd-
routines of course. g-
routines
are slower (by about a factor of 3) because they require floating
point calculation. Whereas gd-
routines make no garbage
during normal drawing, g-
routines make garbage because
normal floating point operations generate garbage in MCL. If desired,
this can be avoided by using MCL's short-float
data type.
If the coordinate system is set using short floats then calls to
g-
routines using coordinates that are also short-floats
will generate no garbage (speed is unaffected). A good future project
might be to convert G internally to use Eran Gat's floating point
compiler, which can speed up floating point calculations significantly,
but may require some care to be used successfully.