Red by Example - an accessible reference by example

Last update on 20-Dec-2019

draw

index     parse     vid     series     draw     help     about     links     contact     
1. DRAWING WITH RED
1.1. Introduction
2.1. Drawing A Line and Box On A Canvas
2.2. Pens, Fill-pens, color
2.3. Drawing Text
3.1. The Draw Block - Introduction
3.2. The Draw Block - Variables, Compose, Reduce
3.3. A 'do' Block For Initialisation
4.1. Animation: Rate, On-time
4.2. The Bouncing Ball Program
5.1. Mouse Coordinates, Events, All-over
5.2. Shape Sub-dialect
6.1 Transformations On Drawings

1. DRAWING WITH RED

1.1. Introduction


The Draw dialect is a domain-specific language intended to simplify
drawing. The language was created in Red itself. However, when you draw
lines, shapes, animations, you will also make use of Red, and of VID (to
place and set up GUI widgets - 'faces'). Here, we will work through some
typical graphics tasks, and introduce - or remind you - about relevant Red
and VID facilities.

This page is only a sampling of Draw. It does not cover ways of speeding-up
animations, or the full range of facilities. Draw has many commands, and
some of these have many options - so you will need to explore its official
documentation:

Draw:
https://doc.red-lang.org/en/draw.html


There are some impressive demos on:

http://www.red-lang.org/2016/03/060-red-gui-system.html


You might also need VID:

Official:
https://doc.red-lang.org/en/vid.html


Red-by-example:
http://www.red-by-example.org/vid.html


When building a GUI with tightly-linked widgets, it can be very useful to
use Red's reactive programming facilities, as shown here:


http://www.red-lang.org/2016/06/061-reactive-programming.html


Thanks to Gregg Irwin for suggestions:

https://gist.github.com/greggirwin


top

2.1. Drawing A Line and Box On A Canvas


Here is the code for our first program:

Red [ needs 'view ]

view [
canvas: base 150x150 draw [line 10x0 160x50]
]

Screenshot:
draw-line1.png

The program needs to incorporate View facilities, at the top.

We use a VID view to set up and place faces (GUI widgets, controls) on the
window. Here, we only use a base (though we could also draw on an
image). We chose to name it 'canvas'.

Following the draw, we have a block of drawing commands, with values if
necessary. Here we draw a line from 10x0 to 160x50. Note that the
line is too long, and the extra part is clipped.

The top left of the base is (0, 0), and in Red we use pair! notation -
such as 0x0.

Next we extend the program to draw a box, providing its top-left and
bottom-right coordinates. We have also used a VID option to make the
canvas white.

Red [ needs 'view ]

view [
canvas: base 150x150 white draw [
line 10x0 160x50
box 10x10 100x80
]
]

Screenshot:
draw-line-and-box.png

In fact, a box can be created by specifying any 2 opposite corners (such as
bottom-left and top-right).

There are lots of shapes, such as circle, ellipse, polygon, etc. Details
are in the Draw official documentation.

Note that we provide values - not variables, expressions - for the shape
coordinates. We cannot put, for example:

xy-coords: 22x33

draw [line xy-coords 100x100] ;-- no!

Later, we will see how to work with this.

Drawing commands can be placed in nested blocks, as in:

[[line 10x0 160x50] [box 10x10 100x80]]

The end result is the same, but the structure can be useful if we need to
process shapes (e.g. deleting a particular shape).

top

2.2. Pens, Fill-pens, color


Drawing lines and unfilled shapes is done with a pen, whereas filled
shapes are drawn with a fill-pen.

They have default settings (a 1-pixel black line etc.) but we can provide
new settings, such as colour, width, and thickness.

Here is an example:

Red [ needs 'view ]
view [
canvas: base 200x200 white draw [
pen blue line-width 8 line 10x0 160x50
pen red box 10x10 100x60
fill-pen pink box 10x80 120x140
pen off box 10x150 120x190
]
]

Screenshot:
draw-pens.png

We created a blue pen with an 8-pixel width, and drew a line. The pen
command must be followed immediately by a colour.

We altered the pen to red, and drew a box, unfilled.

We set the fill-pen to pink, then drew a box - note the red edges.

We turned the pen off, then drew a pink box. There are no edges on the box.

There are more pen options, such as specifying the shape of the line-end,
and how joins appear.

Note also that we can draw on an image widget, as in :

Red [needs 'View]

view [
photo: image 250x250 %country.png
draw [fill-pen gold pen off circle 100x50 25] ;-- add sun
]

Screenshot, showing the added 'sun':
draw-sun.png

top

2.3. Drawing Text


We can incorporate text in a drawing, as follows:

Red [needs 'View ]
view [
canvas: base 150x150 white
draw [text 50x50 "Hello!!!!" circle 70x60 40]
]

Screenshot:
draw-text.png

If your text in not part of the drawing (such as instructions, captions on
faces - widgets) - you can use the VID text face.

It is also possible to specify fonts and sizes.

top

3.1. The Draw Block - Introduction


In the earlier examples, we followed 'draw' with a block of commands. This
is the 'draw-block'. We can add or remove items to it, as required.

Here, we introduce a button, and show how to use a draw-block. We have the
code:

Red [needs 'view]

do-but1: function [] [
print ["canvas/draw-before: " canvas/draw]
print ["size .. " canvas/size]
canvas/size: 160x200
canvas/draw: [fill-pen blue box 10x10 60x60]
print ["canvas/draw-after: " canvas/draw]
]

view [
b1: button "draw" [ do-but1]
return
canvas: base 200x200 white draw [ box 44x44 88x88]
]

Running it shows:
draw-block1.png

When we click the button, the canvas goes smaller, and we see a blue box:
draw-block2.png

We also see this printed on the console:

canvas/draw-before: box 44x44 88x88
size .. 200x200
canvas/draw-after: fill-pen blue box 10x10 60x60

Note the printing: we can access options using the /refinement notation, as
in canvas/size. The /draw contains a block of drawing instructions. It
only holds one block, so when we assign the instructions to draw a blue box,
the previous state is erased. However it is easy to add drawing
instructions to the block, like this:

canvas/draw: [fill-pen blue box 10x10 60x60]
append canvas/draw [box 33x33 170x55] ;-- added this - another blue
box
print ["canvas/draw-after: " canvas/draw].

It draws 2 blue boxes, and prints:

canvas/draw-before: box 44x44 88x88
size .. 200x200
canvas/draw-after: fill-pen blue box 10x10 60x60 box 33x33 170x55

We could use append/only if we wanted to keep each shape in its own block,
to allow access to individual shapes.

Here is another example, which initially draws an unfilled box. It detects
the on-down (mouse-click) event of the base, then draws a red filled
box. The initial box vanishes of course, because the draw block is
completely replaced by a new one.

Red [ needs 'view ]

in-canvas: function [] [
canvas/draw: [ fill-pen red box 2x2 30x30]
]

view [
canvas: base 200x200 white draw [ box 44x44 88x88]
on-down[in-canvas]
]

In summary, when the draw-block is drawn, the previous drawing is erased,
and the new drawing is created. If you want something drawn, put it in the
block.

The draw-block thus contains the whole drawing, as text. You can manipulate
it, for example to add an 'undo' facility, or to select part of the drawing.

top

3.2. The Draw Block - Variables, Compose, Reduce


What if we want to use a variable, as in:

top-left: 22x33
draw [ line top-left 100x100]

We can't do this - the draw block must use values. We need to do some kind
of evaluation first, and in Red, we can use compose and reduce. They
are similar, and here is a summary:

The word reduce returns a copy of a block, evaluating all expressions
in the block.

The word compose returns a copy of a block, evaluating only paren!
items. A paren! type is surrounded in (parentheses).

We can also use Red symbols. For example the symbol:

'elephant

evaluates to:

elephant

Here are some examples:

>> a: 11 b: 22 ;-- create some variables and a block
>> blk: [line (a + b) a + b "message" 123]
>> compose blk
== [line 33 a + b "message" 123] ;-- note (...) effect
>>
>> ;-- now try reduce, on same block
== [line (a + b) a + b "message" 123]
>> reduce blk
*** Script Error: line has no value

>> ;-- try another block, with a 'symbol
>> blk: ['line a b]
>> reduce blk
== [line 11 22] ;-- what we want

Here is a practical example - we want to use a variable for the start of a
line. We can use compose or reduce:

top-left: 4x0

compose [pen red line (top-left) 3x66] ;compose only ( )
reduce ['pen red 'line top-left 3x66]

Either of these results in:

pen 255.0.0 line 4x0 3x66

Here is a program which draws a line at a different place, with every
button-click. Note that we cannot append to an uninitialised block, so we
have used the VID 'do' feature for some once-only initialisation.

Red [needs 'view]
line-end: 100x100

do-but1: func [] [ ;--func allows easier global use
append canvas/draw compose [line 0x100 (line-end)]
line-end: line-end + 0x10
]

view [
b1: button "draw" [do-but1]
return
canvas: base 200x200 white

do [ ;-- initialisation
canvas/draw: copy []
]
]

Screenshot:
draw-block-fan.png

Here is the same basic program, but one click causes 10 lines to be drawn
rapidly, without any user-interaction. The button-click code is:


do-but1: func [] [
line-end: 100x100
loop 10 [
append canvas/draw compose [line 0x100 (line-end)]
line-end: line-end + 0x10
]
]

top

3.3. A 'do' Block For Initialisation


Sometimes it is useful to initialise variables and faces. We can add
a 'do' block at the bottom of view. It contains Red code, as in:

Red [needs 'View]
b: false
... etc

view [
canvas: base white 350x350
; ...etc

do [
canvas/draw: [box 10x10 30x30] ;-- initial shape
b: true
]
]


top

4.1. Animation: Rate, On-time


Each face can have a rate timer, which can be made to tick regularly,
or wait for a duration.

An integer value (above 0) sets a frequency (per second), a time! type
sets a duration, and none stops it. Here, we set the canvas rate to 1
(per second), and detect the tick via the on-time event. The blue square
moves diagonally across the screen, to the bottom-right.

Red [ needs 'view ]

top-left: 10x10

canvas-tick: func [] [
top-left: top-left + 8x8
canvas/draw: reduce ['fill-pen blue 'box top-left top-left + 55x55]
]

view [
canvas: base 200x200 white rate 1
on-time [canvas-tick]
]

The animation is jerky, but try changing the increment to e.g. 1x1, and the
rate to 20, for a gliding blue box.

top

4.2. The Bouncing Ball Program


This is a standard animation example. A blue ball bounces around the
screen. You can stop and start it with buttons - it manipulates the rate
to do this. The items you might wish to play around with have been
initialised at the top of the code.

It could be improved - the testing for wall contact involves the centre of
the ball, whereas it should be the edge. Some work for you to do!

Red [needs 'view]

draw-freq: 25 ;-- init
centre: 10x10
x-step: 1
y-step: 3

in-canvas: func [] [ ;-- for every tick
canvas/rate: draw-freq
;-- hit an edge? And add a bit of randomness, for fun.
if centre/x > 300 [x-step: -1 * x-step - random 0.2]
if centre/y > 300 [y-step: -1 * y-step - random 0.2]
if centre/x < 0 [x-step: -1 * x-step]
if centre/y < 0 [y-step: -1 * y-step]
incr: to-pair reduce [(3 * x-step) (3 * y-step)]
centre: centre + incr
canvas/draw: reduce ['fill-pen blue 'circle centre 8]
;-- use append in above line, if you want a ball trail
]

do-stop: func [] [
canvas/rate: none ;-- stop timer
]

do-start: func [] [
canvas/rate: draw-freq ;starts
]

view [
stop-btn: button "stop" [do-stop]
start-btn: button "start" [do-start]
return

;-- rate set in do-block, below
canvas: base 300x300 white
on-time [in-canvas]

do [ ;-- init
canvas/rate: draw-freq
canvas/draw: copy [] ;-- for the first append
]
]

top

5.1. Mouse Coordinates, Events, All-over


When drawing with user-interaction, we can use buttons, etc, and it can also
be useful to use the mouse position. Here is a fragment of code. When we
click on the base, 'do-down' is called, passing the event object into 'ev'.
The /offset refinement provides the position where the left mouse button was
depressed.

do-down: func [ev] [
xy-pos: ev/offset ;-- mouse position - a pair
;-- etc...
]

view [
canvas: base 300x300 white on-down [do-down event]
]

Here is a program which draws straight lines between successive mouse-clicks:

Red [needs 'View
title: "Sketch with mouse"
]

sketching: false

do-down: func [ev] [
xy-New: ev/offset ;-- mouse position - pair
either not sketching [ ;-- first time?
sketching: true
xy-Start: xy-New ;-- no visible line
] [
append canvas/draw compose [line (xy-Start) (xy-New)]
xy-Start: xy-New ;-- remember where we were
]
]

clear-canvas: func [ev] [
sketching: false
xy-Start: ev/offset
canvas/draw: copy [pen red line-width 7]
]

view [
break-btn: button "Break line" [sketching: false]
clear-btn: button "Clear" [clear-canvas event]
return
canvas: base 300x200 white draw [pen red line-width 7]
on-down [do-down event] ;-- click in canvas?
]

And a screenshot:
draw-red-mouse.png

Here is another example which uses the VID all-over command. This
allows us to pick up mouse-move events.

This program lets you click anywhere on the canvas, and drag open a box to
the required size.

Red [needs 'View]

mouse-state: 'up
draw-block: copy []
coord1: 0x0

do-down: func [event] [
mouse-state: 'down
coord1: event/offset
]

do-up: func [] [
mouse-state: 'up
]

do-move: func [event] [
if mouse-state = 'down [ ;-- and over the canvas
draw-block: copy []
draw-block: compose [box (coord1) (event/offset)] ;based on mouse
canvas/draw: draw-block
]
]

view [
canvas: base 200x200 white all-over ;-- nb all-over
on-up [do-up]
on-down [do-down event]
on-over [do-move event]
]

top

5.2. Shape Sub-dialect


The Shape sub-dialect has similar commands to Draw, but they can be more
convenient, such as:

- automatically closing shapes

- relative drawing, from the previous point. This is done by using the
lit-word version - for example 'line rather than line.

We enclose the commands in a block, after 'shape'. Here is some code

Red [needs 'View]

;-- shape dialect
do-down: func [] [
canvas/draw: [
line-width 5 shape [
line 0x0 90x30 30x90
'hline 90 'vline 22
]
]
]

view [
start-btn: button "Draw" [do-down]
return
canvas: base 300x200 white draw [line-width 7]
]

- It draws 2 lines - note the series of points - (3 here, not 4) - for
the line command.

- It uses the relative versions 'hline, 'vline to draw horizontal and
vertical lines, with one parameter specified.

- It closes the shape (the long 45 degree diagonal line).


Screenshot:
draw-sub-dialect.png

The official doc is at:

https://doc.red-lang.org/en/draw.html#_shape_commands


top

6.1 Transformations On Drawings


Red provides facilities to transform drawings, such as scaling, rotating.
There are also facilities to work with transformation matrices directly.

In general, the transformations work on all draw commands which follow them.
Alternatively, you can restrict the transformations to a block.

Here is the basis of a clock program. It initially draws a vertical line,
and then rotates it by 6 degrees, about one of its ends. The rate of the
canvas tick is once per second, so effectively, we have a clock second-hand.

Red [needs 'View ]

do-rot: func [] [
canvas/draw: compose[rotate 6 150x150 (canvas/draw) ]
]

view[
canvas: base 330x330 white rate 1
draw [line 150x150 150x10]
on-time [do-rot]
]


Note that we chose to rotate the current state of canvas/draw, rather than
incrementing an 'angle' variable.

This concludes our brief sampling of Red's Draw dialect. Look at the
introduction above for more links etc.