(*
    The widget interface will consist of a type composed of a function that processes events
        (signature: fun mouse_x mouse_y mouse_button key_pressed -> returns a new widget)
        and a function to draw the widget itself.
*)
type widget =
{
        process_event : (int -> int -> bool -> char -> widget);
        draw_self : (unit -> unit)
}


(*        Two different types of button: *)

(*        A rectangular fixed-position button *)
let rec make_button left top width height col =
        let ds () = 
        Graphics.set_color col;
        Graphics.draw_rect left top width height
        and pe x y b k = 
                let pt_in_box x y left top width height =
                        if ((x >= left)        && (x < (left + width)) && (y >= top) && (y < (top + height))) then true else false
                in        
                if (pt_in_box x y left top width height) then 
                        make_button left top width height Graphics.red  (* The button has the focus; create a new Red button *)
                else        
                        make_button left top width height Graphics.black (* The button does not have the focus; create a new Black button *)
        in
        {
                process_event = pe;
                draw_self = ds;
        }        

        
(*        And a circular 'draggable' button *)        
let rec make_circle_button cx cy cr col =
        let ds () = 
        Graphics.set_color col;
        Graphics.draw_circle cx cy cr
        and pe x y b k = 
        let pt_in_circle x y cx cy cr =
                let foi = float_of_int and iof = int_of_float in
                        let l = iof (sqrt (foi ((cx - x)*(cx - x) + (cy - y) * (cy - y)))) in
                        if l < cr then true else false        
                in
                if (pt_in_circle x y cx cy cr) then 
                        if b = true then
                                make_circle_button x y cr Graphics.yellow (* The button has the focus *and* is being dragged; create a new Yellow button *)
                        else        
                                make_circle_button cx cy cr Graphics.red (* The button has the focus; create a new Red button *)
                else        
                        make_circle_button cx cy cr Graphics.black (* The button does not have the focus; create a new Black button *)
        in
        {
                process_event = pe;
                draw_self = ds;
        }                
        
        
(* Put them together *)        
let rec do_events w =
        let x, y = Graphics.mouse_pos ()
        and b = Graphics.button_down () 
        and k = if Graphics.key_pressed () then Graphics.read_key () else char_of_int 0 
        in
        if k = char_of_int 27 then exit 0 else (* ESC exits program *)
        let w' = Array.map (fun a -> a.process_event x y b k) w in
        Array.iter (fun a -> a.draw_self ()) w';
        do_events w';;


(* Place all parent buttons in a list. Each parent button is the ancestor of a linear family tree of buttons *)
let get_events () =
        try
        do_events [|(make_circle_button 80 80 25 Graphics.black); 
                                (make_button 10 10 40 40 Graphics.black); 
                                (make_circle_button 180 140 25 Graphics.black); 
                                (make_button 10 80 40 40 Graphics.black)|]
        with
        _ -> ()


(*************** test framework ********************)
let width = 320
let height = 200

 
let main () = 
        let init_string = (Printf.sprintf " %dx%d" width height) in  
        Graphics.open_graph init_string ;  
        let _ = get_events () in
        try   
        while true do
                ()
        done;
        exit 0;
        with
        _ -> print_string "Exception"; Graphics.close_graph (); exit 0
        
;;   
 
main ();;