;;; Primitive GUI for Logo games.

;;; Displays buttons, then accepts mouseclicks or keystrokes
;;; to control actions.

;;; To clear the screen and all the remembered buttons:
;;;   init.buttons

;;; To install a button:
;;;   SETBUTTON [150 130] [40 25] [make "mysecret "true throw "newgame] ~
;;;             :mysecret 0 [Logo guess] []
;;;
;;; Inputs are:
;;;   1. Position of lower left corner of button
;;;   2. Size [x y] of button
;;;   3. Action to take if button pressed
;;;   4. TRUE if box should be drawn thick, FALSE if thin
;;;   5. Color to fill box (0 if no fill)
;;;   6. Text caption inside button (a word or a two-word list for
;;;        a two-line caption) or empty list for no caption
;;;   7. Equivalent keystroke (empty list if no equivalent keystroke)
;;;        (DEL means char 8 or 127; RET means char 10 or 13)
;;;        (Keystroke inside list, e.g., [X], means don't draw it.)

;;; REBUTTON (same inputs as SETBUTTON) looks for existing matching button
;;; and, if found, just redraws border (possibly changing thickness).

;;; To display a descriptive caption (e.g., for a group of buttons)
;;; without making a button:
;;;   CAPTION [150 130] [40 25] [Number |of boxes:|]
;;;   CENTER.CAPTION [150 130] [40 25] [Number |of boxes:|]
;;;
;;; Inputs are position, size, caption.
;;; CENTER.CAPTION centers the text within the box; CAPTION puts it
;;; at the left edge of the box.

;;; To loop reading keystrokes or mouseclicks and taking actions as set:
;;;   ACTION.LOOP
;;; Within an action, :CHAR is the character typed (or zero if the action
;;; was triggered by a mouse click), :BUTTON is the mouse button pressed
;;; (or zero if the action was triggered by a keystroke), and :MOUSEPOS is
;;; the mouse position (or unspecified for a keystroke).  Actions triggered
;;; by a mouse click happen when the mouse button is released.

; -----------------------------

;;; IMPORTANT:  Here is how we know the size of a text character as
;;; drawn by the LABEL command.  Change these numbers if necessary:

make "labelcharsize ifelse equalp :LogoPlatform "Windows [[8 13]] [[6 11]]
; if equalp :LogoPlatform "wxWidgets [make "labelcharsize [7 14]]
if equalp :LogoPlatform "wxWidgets [make "labelcharsize labelsize]

; -----------------------------

to init.buttons
cs ht
make "buttons []
end

; -----------------------------

to setbutton :corner :size :action :thickp :fillcolor :caption :key
center.caption :corner :size :caption
pu setpos :corner
seth 0
pd setpensize ifelse :thickp [[3 3]] [[1 1]]
setpc 7
apply "button.rectangle :size
setpensize [1 1]
if not equalp :fillcolor 0 [
  button.offset :corner 5 5
  setpc :fillcolor
  fill
  setpc 7
]
if (and (not listp :key) (not emptyp :key) (not equalp :key :caption)) [
  caption (list (sum first :corner first :size 4) last :corner) ~
                 (list (first :labelcharsize) (last :size)) ~
                 :key
]
if and (listp :key) (not emptyp :key) [make "key first :key]
push "buttons (list :corner :size :key :action)
end

to rebutton :corner :size :action :thickp :fillcolor :caption :key
localmake "thekey :key
if and listp :key not emptyp :key [make "thekey first :key]
localmake "test (list :corner :size :thekey :action)
localmake "button find [equalp ? :test] :buttons
if emptyp :button ~
   [setbutton :corner :size :action :thickp :fillcolor :caption :key  stop]
penup setpos :corner
seth 0
setpc 7
penerase setpensize [3 3]
apply "button.rectangle :size
penpaint setpensize ifelse :thickp [[3 3]] [[1 1]]
apply "button.rectangle :size
setpensize [1 1]
end

to button.offset :corner :dx :dy
penup setxy (first :corner)+:dx (last :corner)+:dy
end

to button.rectangle :x :y
repeat 2 [fd :y rt 90 fd :x rt 90]
end

; -----------------------------

to caption :corner :size :caption
if emptyp :caption [stop]
localmake "oldscrunch scrunch
if not namep "caption.scrunch [localmake "caption.scrunch 1]
localmake "myscrunch map [? * :caption.scrunch] :oldscrunch
localmake "y (last :labelcharsize)*:caption.scrunch
setpc 7
ifelse listp :caption [
  button.offset :corner 0 ((14-:y)+((last :size)-25)/3)
  if :caption.scrunch <> 1 [apply "setscrunch :myscrunch]
  label last :caption
  if :caption.scrunch <> 1 [apply "setscrunch :oldscrunch]
  button.offset :corner 0 ((14-:y)+:y+2*((last :size)-25)/3)
  if :caption.scrunch <> 1 [apply "setscrunch :myscrunch]
  label first :caption
  if :caption.scrunch <> 1 [apply "setscrunch :oldscrunch]
] [
  button.offset :corner 0 ((17-:y)+((last :size)-17)/2)
  if :caption.scrunch <> 1 [apply "setscrunch :myscrunch]
  label :caption
  if :caption.scrunch <> 1 [apply "setscrunch :oldscrunch]
]
end

to center.caption :corner :size :caption
if emptyp :caption [stop]
localmake "oldscrunch scrunch
if not namep "caption.scrunch [localmake "caption.scrunch 1]
localmake "myscrunch map [? * :caption.scrunch] :oldscrunch
localmake "halfx (first :labelcharsize)*:caption.scrunch/2
localmake "y (last :labelcharsize)*:caption.scrunch
setpc 7
ifelse listp :caption [
  button.offset :corner (1+(first :size)/2-:halfx*(count last :caption)) ~
                        ((14-:y)+((last :size)-25)/3)
  if :caption.scrunch <> 1 [apply "setscrunch :myscrunch]
  label last :caption
  if :caption.scrunch <> 1 [apply "setscrunch :oldscrunch]
  button.offset :corner (1+(first :size)/2-:halfx*(count first :caption)) ~
                        ((14-:y)+:y+2*((last :size)-25)/3)
  if :caption.scrunch <> 1 [apply "setscrunch :myscrunch]
  label first :caption
  if :caption.scrunch <> 1 [apply "setscrunch :oldscrunch]
] [
  button.offset :corner (1+(first :size)/2-:halfx*(count :caption)) ~
                        ((17-:y)+((last :size)-17)/2)
  if :caption.scrunch <> 1 [apply "setscrunch :myscrunch]
  label :caption
  if :caption.scrunch <> 1 [apply "setscrunch :oldscrunch]
]
end

; -----------------------------

to action.loop [:buttonact [button.mouseclick]] [:keyact [button.readkey]]
action.once
forever [wait 100]
end

to action.off
ern [buttonact keyact]
ern "keyact
end

to action.once
if keyp [button.readkey]
if buttonp [button.mouseclick]
end

to button.readkey [:char rc] [:button 0] [:buttonact []] [:keyact []]
foreach :buttons [
  localmake "key item 3 ?
  ifelse equalp :key "DEL [
    if memberp (ascii :char) [8 127] [run last ? action.once stop]
  ] [
    ifelse equalp :key "RET [
      if memberp (ascii :char) [10 13] [run last ? action.once stop]
    ] [
      if equalp :char :key [run last ? action.once stop]
    ]
  ]
]
end

to button.mouseclick [:mousepos clickpos] [:button button] [:char 0] ~
                     [:buttonact []] [:keyact []]
while [buttonp] []	; wait for release of button
foreach :buttons [
  if apply "button.inrange ? [run last ? action.once stop]
]
end

to button.inrange :corner :size :key :action
(foreach :mousepos :corner :size [
   if ?1 < ?2 [output "false]
   if ?1 > (?2 + ?3) [output "false]
])
output "true
end
