Last active
June 17, 2016 23:24
-
-
Save greggirwin/659e394581ae95b543db76042d3e98a4 to your computer and use it in GitHub Desktop.
Analog clock, refined, with informational comments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Red [ | |
Title: "Red O'clock" | |
Author: "Gregg Irwin" | |
File: %analog-clock.red | |
Tabs: 4 | |
Needs: View | |
Purpose: { | |
- Introduce some basic Red concepts, like functions and datatypes | |
- Show how the DRAW block and timer events work in the View system | |
} | |
Notes: { | |
ii comments are informational, for those learning Red. | |
} | |
] | |
;ii A simple function with a doc string that says what it does. | |
;ii No type delcaration for the param. | |
sex-to-degree: func ["Sexagesimal to degrees" n][n * 6] | |
;ii A simple function with doc strings for the params. | |
;ii No type delcarations for the params. | |
;ii Parens aren't required here, just added for clarity. | |
degree-to-xy: func [rad "radius" deg "degrees"][ | |
as-pair (rad * sine deg) (rad * negate cosine deg) | |
] | |
;ii Now a func with no doc strings (they are optional), but | |
;ii with a type declaration for the param. | |
;ii Parens are used to control evaluation order. Red has strict | |
;ii left-to-right evaluation for infix operators. There are no | |
;ii special precedence rules for different ops. This is different | |
;ii than many other languages. Use parens or reorder ops as needed. | |
hour-to-tick: func [t [time!]][ | |
; Positioning the hour hand isn't as easy as using the hour value | |
; directly, because it's not sexagesimal and we only have 12 hours | |
; on the clock for a 24 hour period. It's also nice if it doesn't | |
; just jump from one hour mark (= 5 ticks) to the next, but moves | |
; gradually between them based on the number of minutes. | |
5 * ((t/hour // 12) + ((to float! t/minute) / 60)) | |
] | |
;ii Set up some global vars. Note that dividing a pair! by an integer | |
;ii results in a pair. That is, both /x and /y are divided. | |
outer-wd: 4 ; thickness of outer ring | |
size: 200x200 ; overall clock size | |
radius: divide size/x 2 | |
center: size / 2 | |
;ii A small block we evaluate, and use later to map hand names to lengths. | |
hand-len: reduce ['hour radius * .65 'min radius * .85 'sec radius * .8] | |
;ii Now a func with both doc strings and type declarations. | |
update-hand: function [ | |
"Updates each hand by changing its line command in the draw block." | |
hand [word!] "Maps to position in draw block" | |
tick [number!] "0-60" | |
][ | |
; Position in draw block | |
pos: get select [hour hour-idx min min-idx sec sec-idx] hand | |
change pos reduce [ | |
'line center (center + (degree-to-xy hand-len/:hand (sex-to-degree tick))) | |
] | |
] | |
;ii HAS declares a func with local words, but no params. | |
;ii DOES (not seen here) declares a func with neither local words or params. | |
update-hands: has [t][ | |
t: now/time | |
update-hand 'hour hour-to-tick t | |
update-hand 'min t/minute + ((to float! t/second) / 60) | |
update-hand 'sec t/second | |
] | |
;ii COMPOSE evaluates only the parens in the block, giving us a way to | |
;ii control evaluation precisely. The rest of the values are literal and | |
;ii make up commands for the DRAW dialect in the View system. | |
;ii | |
;ii See: https://github.com/red/red/wiki/Draw-dialect | |
;ii | |
; Start with the outer circle | |
draw-blk: compose [ | |
pen red line-cap round | |
line-width (outer-wd) fill-pen white circle (center) (radius - outer-wd) | |
line-width 2 ; tick mark width | |
] | |
;ii We add more data to the block, but we're not using it yet. It's | |
;ii just data. | |
; Add tick marks | |
repeat i 60 [ | |
;ii We could do modulo calcs on i to set the tick mark length, | |
;ii but SWITCH can dispatch on multiple values, making fixed | |
;ii cases clear and obvious. The last block [7] is the default. | |
tick-len: switch/default i [ | |
15 30 45 60 [25] | |
5 10 20 25 35 40 50 55 [15] | |
][7] | |
;ii Remember that 'center is a pair! value (100x100), and | |
;ii DEGREE-TO-XY also returns a pair!, which we can add to | |
;ii 'center directly. | |
p1: center + (degree-to-xy (radius - outer-wd) (sex-to-degree i)) | |
p2: center + (degree-to-xy (radius - tick-len - outer-wd) (sex-to-degree i)) | |
;ii This is where we add the DRAW command for each tick mark. We | |
;ii calc the position above, then REPEND (= reduce+append) it to | |
;ii the block of commands we started with. Notice how 'line has | |
;ii a single quote on the front. That identifies it as a literal | |
;ii word value, or lit-word! type. When we reduce it, it becomes | |
;ii a regular word. | |
repend draw-blk ['line p1 p2] | |
] | |
; We add the setup for each hand to the draw block, mark that position, | |
; and then update the draw block on each tick. Note that set-word!s in | |
; DRAW blocks don't get set until the block is evaluated by View. | |
append draw-blk compose [ | |
pen brick line-cap round | |
line-width 4 hour-idx: line 0x0 0x0 ; set-word! is a marker, 0x0 vals are placeholders | |
line-width 3 min-idx: line 0x0 0x0 | |
pen maroon | |
line-width 1 sec-idx: line 0x0 0x0 | |
fill-pen brick ; comment this out for a hollow center dot | |
circle (center) 3 | |
] | |
;ii Now we're actually going to do something. VIEW takes a layout | |
;ii specification, written in the VID dialect, evaluates it, creates | |
;ii the window and faces (called widgets or controls in other UIs), | |
;ii binds actions, and starts the GUI event loop. | |
;ii | |
;ii See: https://github.com/red/red/wiki/VID-Reference-Documentation | |
;ii | |
;ii VID is a wrapper over the View Graphic System, making it easy to | |
;ii write concise UIs without having to specify everything in detail. | |
;ii | |
;ii See: https://github.com/red/red/wiki/Red-View-Graphic-System | |
;ii | |
;ii In this case we have a blank (base) face that we draw on. It gets | |
;ii timer events every second (rate 1), which are handled by an actor | |
;ii for the on-time event. [update-hands] is the body of that actor | |
;ii function, which gets passed both the face object that got the | |
;ii event, and the event details. We don't use them here, but just | |
;ii call the UPDATE-HANDS function we defined earlier. | |
view compose/only [ | |
size (size) | |
origin 0x0 | |
clock: base (size) draw (draw-blk) rate 1 on-time [update-hands] | |
do [clock/color: none] | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment