Task

Draw a clock.



More specific:

 Draw a time keeping device. It can be a stopwatch, hourglass, sundial, a mouth counting "one thousand and one", anything. Only showing the seconds is required, e.g.: a watch with just a second hand will suffice. However, it must clearly change every second, and the change must cycle every so often (one minute, 30 seconds, etc.) It must be drawn; printing a string of numbers to your terminal doesn't qualify. Both text-based and graphical drawing are OK.
 The clock is unlikely to be used to control space flights, so it needs not be hyper-accurate, but it should be usable, meaning if one can read the seconds off the clock, it must agree with the system clock.
 A clock is rarely (never?) a major application: don't be a CPU hog and poll the system timer every microsecond, use a proper timer/signal/event from your system or language instead. For a bad example, many OpenGL programs update the frame-buffer in a busy loop even if no redraw is needed, which is very undesirable for this task.
 A clock is rarely (never?) a major application: try to keep your code simple and to the point. Don't write something too elaborate or convoluted, instead do whatever is natural, concise and clear in your language.


Key points
 animate simple object
 timed event 
 polling system resources 
 code clarity


--
-- demo\rosetta\Clock.exw
-- ======================
--
with javascript_semantics
include pGUI.e
Ihandle dlg, canvas, hTimer
cdCanvas cd_canvas

procedure draw_hand(atom degrees, atom r, baseangle, baselen, cx, cy)
    atom a = PI-(degrees+90)*PI/180,
         -- tip
         x1 = cos(a)*(r),
         y1 = sin(a)*(r),
         -- base
         x2 = cos(a+PI-baseangle)*baselen,
         y2 = sin(a+PI-baseangle)*baselen,
         x3 = cos(a+PI+baseangle)*baselen,
         y3 = sin(a+PI+baseangle)*baselen
    cdCanvasSetLineWidth(cd_canvas,1)
    cdCanvasLine(cd_canvas,cx+x1,cy+y1,cx+x2,cy+y2)
    cdCanvasLine(cd_canvas,cx+x2,cy+y2,cx+x3,cy+y3)
    cdCanvasLine(cd_canvas,cx+x3,cy+y3,cx+x1,cy+y1)
    cdCanvasBegin(cd_canvas,CD_FILL)
    cdCanvasVertex(cd_canvas,cx+x1,cy+y1)
    cdCanvasVertex(cd_canvas,cx+x2,cy+y2)
    cdCanvasVertex(cd_canvas,cx+x3,cy+y3)
    cdCanvasEnd(cd_canvas)
end procedure

procedure draw_clock(atom cx, cy, d)
    atom w = 2+floor(d/25)
    cdCanvasFont(cd_canvas, "Helvetica", CD_PLAIN, floor(d/15))
    cdCanvasSetLineWidth(cd_canvas, w) 
    cdCanvasArc(cd_canvas, cx, cy, d, d, 0, 360) 
    d -= w+8
    w = 1+floor(d/50)
    for i=6 to 360 by 6 do
        integer h = remainder(i,30)=0
        cdCanvasSetLineWidth(cd_canvas, max(floor(w*(1+h)/3),1)) 
        atom a = PI-(i+90)*PI/180,
             x1 = cos(a)*d/2, x2 = cos(a)*(d/2-w*(2+h)*.66),
             y1 = sin(a)*d/2, y2 = sin(a)*(d/2-w*(2+h)*.66)
        cdCanvasLine(cd_canvas, cx+x1, cy+y1, cx+x2, cy+y2) 
        if h then
            x1 = cos(a)*(d/2-w*4.5)
            y1 = sin(a)*(d/2-w*4.5)
            cdCanvasText(cd_canvas,cx+x1,cy+y1,sprintf("%d",{i/30}))
        end if
    end for
    atom {hour,mins,secs,msecs} = date(true)[DT_HOUR..DT_MSEC]
    if IupGetInt(hTimer,"TIME")<1000 then
        -- (if showing once a second, always land on exact 
        --  seconds, ie completely ignore msecs, otherwise
        --  show smooth running (fractional) second hand.)
        secs += msecs/1000
    end if
    mins += secs/60
    hour += mins/60
    atom r = d/2
    draw_hand(hour*360/12,r-w*9,0.3,d/20,cx,cy)
    draw_hand(mins*360/60,r-w*2,0.2,d/16,cx,cy)
    cdCanvasSetForeground(cd_canvas, CD_RED)
    draw_hand(secs*360/60,r-w*2,0.05,d/16,cx,cy)
    cdCanvasSetForeground(cd_canvas, CD_BLACK)
end procedure

function redraw_cb(Ihandle /*ih*/, integer /*posx*/, /*posy*/)
    integer {width, height} = IupGetIntInt(canvas, "DRAWSIZE"),
            r = floor(min(width,height)*0.9),
            cx = floor(width/2),
            cy = floor(height/2)
    cdCanvasActivate(cd_canvas)
    cdCanvasClear(cd_canvas) 
    draw_clock(cx,cy,r)
    cdCanvasFlush(cd_canvas)
    return IUP_DEFAULT
end function

function timer_cb(Ihandle /*ih*/)
    IupUpdate(canvas)
    return IUP_IGNORE
end function

function map_cb(Ihandle ih)
    IupGLMakeCurrent(canvas)
    if platform()=JS then
        cd_canvas = cdCreateCanvas(CD_IUP, canvas)
    else
        atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
        cd_canvas = cdCreateCanvas(CD_GL, "10x10 %g", {res})
    end if
    cdCanvasSetBackground(cd_canvas, CD_WHITE)
    cdCanvasSetForeground(cd_canvas, CD_BLACK)
    cdCanvasSetTextAlignment(cd_canvas, CD_CENTER) 
    return IUP_DEFAULT
end function

function canvas_resize_cb(Ihandle /*canvas*/)
    integer {canvas_width, canvas_height} = IupGetIntInt(canvas, "DRAWSIZE")
    atom res = IupGetDouble(NULL, "SCREENDPI")/25.4
    cdCanvasSetAttribute(cd_canvas, "SIZE", "%dx%d %g", {canvas_width, canvas_height, res})
    return IUP_DEFAULT
end function

procedure main()
    IupOpen()

    canvas = IupGLCanvas("RASTERSIZE=350x350")
    IupSetCallbacks(canvas, {"MAP_CB", Icallback("map_cb"),
                             "ACTION", Icallback("redraw_cb"),
                             "RESIZE_CB", Icallback("canvas_resize_cb")})

    hTimer = IupTimer(Icallback("timer_cb"), 40)    -- smooth secs
--  hTimer = IupTimer(Icallback("timer_cb"), 1000)  -- tick seconds

    dlg = IupDialog(canvas, "TITLE=Clock")
    IupShow(dlg)
    IupSetAttribute(canvas, "RASTERSIZE", NULL) -- release the minimum limitation
    if platform()!=JS then
        IupMainLoop()
        IupClose()
    end if
end procedure

main()
