############################################################################
#
#       File:     kaleid.icn
#
#       Subject:  Program to produce kaleidoscope
#
#       Author:   Stephen B. Wampler
#
#       Date:     May 2, 2001
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#    Lots of options, most easily set by with the interface after
#    startup.  The only one that isn't set that way is -wn where 'n' is
#    the size of the kaleidoscope window (default is 600 square).
#
#    Terminology (and options):
#
#       Window_size (-wN): How big of a display window to use.
#           At the current time, this can only be set via a
#           command line argument.
#
#       Density (-dN): How many circles per octant to keep on display
#           at any one time.  There is NO LIMIT to the density.
#
#       Duration (-lN): How long to keep drawing circles (measured in
#           in circles) once the density is reached.  There is NO LIMIT
#           to the duration.
#
#       MaxRadius (-MN): Maximum radius of any circle.
#
#       MinRadius (-mN): Preferred minimum radius.  Circles with centers
#           near the edge have their radii forced down to fit entirely
#           on the display
#
#       MaxOffset (-XN): Maximum offset from center of display (may wrap).
#
#       MinOffset (-xN): Minimum offset
#
#       Skew (-sN): Shift probability of placing a circle at a 'typical'
#           offset.
#
#       Fill (-F): Turns off filling the circles.
#
#       Clear (-C): After the duration, reduces density back to 0 before
#           quitting.
#
#       Random Seed: (-rN): Sets the random number seed.
#
# Thanks to Jon Lipp for help on using vidgets, and to Mary Camaron
#   for her Interface Builder.
#
############################################################################
#
#  Requires:  Version 9 graphics
#
############################################################################
#
#  Links:  vidgets, vslider, vtext, vbuttons, vradio, wopen, xcompat
#
############################################################################

link vidgets
link vslider
link vtext
link vbuttons
link vradio
link wopen
link xcompat

global Clear, fill, duration, density, maxoff, minoff
global maxradius, minradius, r_seed, skew, win_size, mid_win
global root, check1, mainwin, use_dialog
global draw_circle

global du_v, de_v, rs_v, sk_v

procedure main (args)

   draw_circle := DrawCircle

   init_globs()
   process_args(args)

   if \use_dialog then {        # have vidgets, so use them for args.
      mainwin := WOpen("label=Kaleidoscope", "width=404", "height=313", 
                       "font=6x12") |
                 stop ("bad mainwin")
      root := ui (mainwin)
      GetEvents (root, quit)
      }
   else {                       # just rely on command line arguments
      kaleidoscope(r_seed)
      }

end

procedure init_globs()

   duration := 500                    # set default characteristics
   density := 30
   win_size := 600
   minoff := 1
   maxradius := 150
   minradius := 1
   skew := 1
   fill := "On"
   draw_circle := FillCircle
   Clear := "Off"
   r_seed := map("HhMmYy", "Hh:Mm:Yy", &clock)
   # See if the Vidget library is available or not
   if \VSet then use_dialog := "yes"
            else use_dialog := &null

end

procedure process_args(args)
   local arg

   # really only needed if you don't use the dialog box
   every arg := !args do case arg[1+:2] of {
      "-w" : win_size := integer(arg[3:0])       # window size
      "-d" : density := integer(arg[3:0])        # density of circles
      "-l" : duration := integer(arg[3:0])       # duration
      "-M" : maxradius := integer(arg[3:0])      # maximum radius
      "-m" : minradius := integer(arg[3:0])      # minimum radius
      "-X" : maxoff := integer(arg[3:0])         # maximum offset
      "-x" : minoff := integer(arg[3:0])         # minimum offset
      "-s" : skew := numeric(arg[3:0])           # set skewedness
      "-F" : fill := &null                       # turn off fill
      "-C" : Clear := "yes"                      # turn on clear mode
      "-r" : r_seed := integer(arg[3:0])         # random seed
      "-h" : stop("usage: kal [-wn] [-dn] [-ln] [-Mn] [-mn] [-Xn] [-xn] _
                     [-sn] [-F] [-C] [-rn]")
      }
   # adjust parameters that depend on the window size...
   mid_win := win_size/2
   maxoff := win_size-1
end

# Lorraine Callahan's kaleidoscope program, translated into icon.
#  (some of the things she did were too sophisticated for me
#   to spend time to figure out, so the output is square instead of
#   round), and I use 'xor' to draw instead of writing to separate
#   bit planes.

global putcircle, clrcircle

procedure kaleidoscope(r)
   local colors

   # What colors to use?  This can be changed to whatever!
   colors := ["red","green","blue","cyan","magenta","yellow"]

   &window := WOpen("label=Kaleidoscope: 'q' quits", "width="||win_size,
                                  "height="||win_size, "bg=black")
   WAttrib("drawop=xor")

   # Create two *indentical* sequences of circles, one to use when
   #   when drawing, one for erasing.  (Since 'xor' is used to
   #   place them, these both just draw the circles!)

   putcircle := create {                # draws sequence of circles
      &random :=: r
      |{
       Fg(?colors)
       outcircle()
       &random <-> r
       }
      }

   clrcircle := create {                # erases sequence of circles
      &random :=: r
      |{
       Fg(?colors)
       outcircle()
       &random <-> r
       }
      }

   every 1 to density do @putcircle     # fill screen to density

   every 1 to duration do {             # maintain steady state
      @putcircle
      @clrcircle
      if *Pending(&window) > 0 then break
      }

   every (Clear == "On") & 1 to density do @clrcircle

   close(&window)
end


procedure outcircle()                   # select a circle at random,
local radius, xoff, yoff                #  draw it in kaleidoscopic form

        # get a random center point and radius
   xoff := (?(maxoff - minoff) + minoff) % mid_win
   yoff := (?(maxoff - minoff) + minoff) % mid_win
   radius := ?0 ^ skew
        # force radius to 'fit'
   radius := ((maxradius-minradius) * radius + minradius) %
             (mid_win - ((xoff < yoff)|xoff))

        # put into all 8 octants
   draw_circle(mid_win+xoff, mid_win+yoff, radius)
   draw_circle(mid_win+xoff, mid_win-yoff, radius)
   draw_circle(mid_win-xoff, mid_win+yoff, radius)
   draw_circle(mid_win-xoff, mid_win-yoff, radius)

   draw_circle(mid_win+yoff, mid_win+xoff, radius)
   draw_circle(mid_win+yoff, mid_win-xoff, radius)
   draw_circle(mid_win-yoff, mid_win+xoff, radius)
   draw_circle(mid_win-yoff, mid_win-xoff, radius)

   return
end


############################################################################
#
#   Vidget-based user interface -- developed originally using Mary
#       Camaron's XIB program.  Don't expect this to be very readable -
#       you should have to play with it!
#
############################################################################
procedure ui (win)
   local cv1, cv2, cv3, cv4
   local 
         radio_button2, 
         radio_button1, 
         text_input6, 
         text_input5, 
         slider4, 
         slider3, 
         text_input4, 
         text_input3, 
         slider2, 
         slider1 

   /win := WOpen("label=ui", "width=404", "height=313", "font=6x12") | 
           stop ("bad win")
   root := Vroot_frame (win)

   VInsert (root, Vmessage(win, win_size/2), 168, 98)
   VInsert (root, Vmessage(win, "1"), 108, 97)

   VInsert (root, sk_v := Vtext(win,"Skew:\\=1",get_skew,,6), 280, 39)

   VInsert (root, du_v := Vtext(win, "Duration:\\="||duration, get_duration,,9),
                237, 15)

   VInsert (root, Vmessage(win, "Clear at end?"), 232, 145)
   VInsert (root, Vmessage(win, "Fill?"), 105, 142)
   VInsert (root, Vmessage(win,"Quit?"), 267, 259)
   VInsert (root, Vmessage(win,"Display it?"), 26, 260)

   VInsert (root, Vcheckbox(win, do_quit, "check2",20), 305, 255, 20, 20)

   VInsert (root, check1:=Vcheckbox(win, do_display, "check1",20),
                106, 258, 20, 20)

   radio_button2 := Vradio_buttons (win, ["On", "Off"], get_clear, , V_CIRCLE)
   VSet(radio_button2,Clear)
   VInsert (root, radio_button2, 253, 165)

   radio_button1 := Vradio_buttons (win, ["On", "Off"], get_fill, , V_CIRCLE)
   VSet(radio_button1,fill)
   VInsert (root, radio_button1, 99, 165)

   cv1 := Vcoupler()
   VAddClient(cv1, get_max_offset)
   text_input6 := Vtext (win, "Max Offset:\\="||(win_size-1), cv1, , 3)
   VAddClient(cv1, text_input6)
   slider4 := Vhoriz_slider (win, cv1, "slider4", 70, 12, 0,
                         win_size-1, win_size-1, )
   VAddClient(cv1, slider4)
   VInsert (root, text_input6, 196, 103)
   VInsert (root, slider4, 306, 106)

   cv2 := Vcoupler()
   VAddClient(cv2, get_min_offset)
   text_input5 := Vtext (win, "Min Offset\\=1", cv2, , 3)
   VAddClient(cv2, text_input5)
   slider3 := Vhoriz_slider (win, cv2, "slider3", 70, 12, 1, win_size-1, 1, )
   VAddClient(cv2, slider3)
   VInsert (root, text_input5, 201, 80)
   VInsert (root, slider3, 307, 82)

   cv3 := Vcoupler()
   VAddClient(cv3, get_max_radius)
   text_input4 := Vtext (win, "Max Radius\\="||(win_size/4), cv3, , 3)
   VAddClient(cv3, text_input4)
   slider2 := Vhoriz_slider (win, cv3, "slider2", 70, 12, 1, win_size/2,
         win_size/4, )
   VAddClient(cv3, slider2)
   VInsert (root, text_input4, 10, 104)
   VInsert (root, slider2, 110, 108)

   cv4 := Vcoupler()
   VAddClient(cv4, get_min_radius)
   text_input3 := Vtext (win, "Min Radius\\=1", cv4, , 3)
   VAddClient(cv4, text_input3)
   slider1 := Vhoriz_slider (win, cv4, "slider1", 70, 12, 1, win_size/2, 1, )
   VAddClient(cv4, slider1)
   VInsert (root, text_input3, 10, 81)
   VInsert (root, slider1, 110, 84)

   VInsert (root, rs_v := Vtext(win,"Random Seed:\\="||r_seed, get_random,, 11),
              30, 41)
   VInsert (root, de_v := Vtext(win,"Density:\\="||density, get_density,,8),
              71, 16)

   VResize (root)
   return root
end

procedure get_skew (wit, value)
   skew := value
end

procedure get_duration (wit, value)
   duration := value
end

procedure do_quit (wit, value)
   stop()
end

procedure do_display (wit, value)
   r_seed   := numeric(rs_v.data)
   duration := integer(du_v.data)
   density  := integer(de_v.data)
   skew     := integer(sk_v.data)
   kaleidoscope(r_seed)
   wit.callback.value := &null
   VDraw(check1)
end

procedure get_clear (wit, value)
   Clear := value
end

procedure get_fill (wit, value)
   fill := value
   if fill == "Off" then draw_circle := DrawCircle
   else draw_circle := FillCircle
end

procedure get_max_offset (wit, value)
   maxoff := value
end

procedure get_min_offset (wit, value)
   minoff := value
end

procedure get_max_radius (wit, value)
   maxradius := value
end

procedure get_min_radius (wit, value)
   minradius := value
end

procedure get_random (wit, value)
   r_seed := integer(value)
end

procedure get_density (wit, value)
   density := integer(value)
end

procedure quit(e)
   if e === "q" then stop ("Exiting Kaleidoscope")
end
