(*
 * Title:    balls64
 * Purpose:  to demonstrate the use of the RISC OS library
 *
 * This application takes the balls64 program, which you may have seen and
 * displays it in a window. We use a sprite to hold the display, and plot
 * this sprite scaled to fit the current size of the window.
 * Left-clicking on the icon will start the display and this can be
 * "frozen/unfrozen" using the main menu. Since we are in a cooperative
 * multi-tasking environment, we display a ball on every null event to
 * avoid "hogging" the CPU
 * 
 *)

Program Balls64;

Label 9999;

#include "wimp.h"        (*  access to WIMP SWIs                      *)
#include "wimpt.h"       (*  wimp task facilities                     *)
#include "win.h"         (*  registering window handlers              *)
#include "event.h"       (*  poll loops, etc                          *)
#include "baricon.h"     (*  putting icon on icon bar                 *)
#include "sprite.h"      (*  sprite operations                        *)
#include "werr.h"        (*  error reporting                          *)
#include "res.h"         (*  access to resources                      *)
#include "resspr.h"      (*  sprite resources                         *)
#include "flex.h"        (*  dynamic mem alloc from WIMP              *)
#include "template.h"    (*  reading in template file                 *)
#include "bbc.h"         (*  olde-style graphics routines             *)
#include "colourtran.h"  (*  interface to colour translation module   *)
#include "os.h"          (*  low-level RISCOS access                  *)
#include "dbox.h"        (*  dialogue box handling                    *)
#include "saveas.h"      (*  data export from dbox by icon dragging   *)
#include "visdelay.h"    (*  show the hourglass for delay             *)

(* --- Conversion macros --- *)
(* These macros convert between sprite coords and work area coords *)

#define balls64_Xtowork(x)  shl((x), 1)
#define balls64_Ytowork(y)  shl((y), 2)

(* --- Sprite Constants --- *)
#define SpriteFile   $0ff9
#define SpriteWidth  610
#define SpriteHeight 230
#define SpriteMode    15
#define SpriteSize   640*256 + size(sprite_header) + size(sprite_area)

(* --- Circle Constants --- *)
#define Radius  64
#define RadDiv2 shr(Radius, 1)
#define Step    shr(Radius, 3)

(* --- Menu Entry Constants --- *)
#define iconmenu_MInfo     1
#define iconmenu_MSave     2
#define iconmenu_MDisplay  3
#define iconmenu_MFreeze   4
#define iconmenu_MQuit     5

type spr_details =
       record
         area : sprite_area_ptr;
         id : sprite_id
       end;

type change_box_handle = ^change_box_ptr;
     change_box_ptr = ^change_box;
     change_box =
       record
         flag : integer;
         box : wimp_box
       end;

(* --- Program Globals --- *)

var my_sprite : spr_details;         (* sprite used for display      *)
    displaywin_handle : wimp_w;      (* display window handle        *)
    save_area : ^integer;            (* save area for sprite context *)

    displaying : boolean;            (* window on display?           *)
    frozen : boolean;                (* window display frozen?       *)
    xdivmult, ydivmult,
    xmagmult, ymagmult : integer;    (* scale to fit window          *)
    trans : array[0..255] of sprite_pixtrans;
                                     (* colour translation table     *)
 

(*************************** SPRITE CREATION *******************************)

procedure balls64_create_sprite(var my_sprite : spr_details);

var save_area_size : integer;
    ptr : sprite_ptr;

begin
  (* --- allocate our own sprite area to hold balls display --- *)

  if not flex_alloc(flex_ptr(address(my_sprite.area)), SpriteSize)
    then werr(TRUE, 'Fatal error - failed to allocate store for sprite');
  sprite_area_initialise(my_sprite.area, SpriteSize);
  
  (* --- create a sprite within that area --- *)

  wimpt_complain(sprite_create(my_sprite.area, 'balldisplay',
                 sprite_nopalette, SpriteWidth, SpriteHeight, SpriteMode));
  my_sprite.id.tag := sprite_id_name;
  my_sprite.id.s.name := 'balldisplay';

  (* --- select the sprite and get a pointer to it (faster) --- *)

  wimpt_complain(sprite_select_rp(my_sprite.area, address(my_sprite.id), ptr));
  my_sprite.id.tag := sprite_id_addr;
  my_sprite.id.s.addr := ptr;
    
  (* --- establish save area size for sprite context and allocate it --- *)
  (* --- also set save area's first word to zero to show it is not   --- *)
  (* --- yet initialised                                             --- *)

  wimpt_complain(sprite_sizeof_spritecontext(my_sprite.area,
                                             address(my_sprite.id),
                                             save_area_size));
  if not flex_alloc(flex_ptr(address(save_area)), save_area_size)
    then werr(TRUE, 'Fatal error - failed to get store for sprite context');
  save_area^ := 0;
end;

(***************************** WINDOW HANDLING *****************************)

procedure balls64_create_displaywin(var handle : wimp_w);

var window : wimp_wind_ptr;

begin

  (* --- find template for our window and create a window from it --- *)
    window := template_syshandle('ballswind');
    wimp_create_wind(window, handle);

end;

procedure balls64_redo_window(r : wimp_redrawstr; more : integer);

var more_to_do : integer;
    new_r : wimp_redrawstr;
    factors : sprite_factors;
    pixtrans : array[0..255] of sprite_pixtrans;

begin

  more_to_do := more;
  new_r := r;
  
  (* --- ask how the WIMP is going to scale our sprite --- *)
  wimp_readpixtrans(my_sprite.area, address(my_sprite.id),
                    address(factors), address(pixtrans[0]));

  (* -- scale the factors according to current window size --- *)
  factors.xdiv := factors.xdiv * xdivmult;
  factors.ydiv := factors.ydiv * ydivmult;
  factors.xmag := factors.xmag * xmagmult;
  factors.ymag := factors.ymag * ymagmult;

  (* --- refresh the window's contents --- *)
  while more_to_do <> 0
    do begin
         wimpt_complain(sprite_put_scaled(my_sprite.area,
                                          address(my_sprite.id), 0,
                                          r.box.x0, r.box.y0,
                                          address(factors),
                                          address(trans[0])));
         wimp_get_rectangle(address(new_r), more_to_do);
       end;
end;

procedure balls64_redraw_window(handle : wimp_w);

var more : integer;
    r : wimp_redrawstr;
    winfo : wimp_winfo;

begin
  
  winfo.w := handle;
  wimp_get_wind_info(address(winfo));

  (* --- establish factors by which to scale sprite from current --- *)
  (* --- window size                                             --- *)
  xdivmult := winfo.info.ex.x1 - winfo.info.ex.x0;
  ydivmult := winfo.info.ex.y1 - winfo.info.ex.y0;
  xmagmult := winfo.info.box.x1 - winfo.info.box.x0;
  ymagmult := winfo.info.box.y1 - winfo.info.box.y0;
  
  (* --- do the redraw --- *)
  r.w := handle;
  wimp_redraw_wind(address(r), more);

  if (more <> 0)
    then balls64_redo_window(r, more);
end;

procedure balls64_update_window(r : wimp_redrawstr);

var new_r : wimp_redrawstr;
    more : integer;

begin

  new_r := r;

  wimp_update_wind(address(new_r), more);
  if (more <> 0)
    then balls64_redo_window(new_r, more);
end;

var old_x, old_y : integer;
  
procedure balls64_open_window(o : wimp_openstr_ptr);

begin
  
  (* --- force scroll offsets to 0, since the window always --- *)
  (* --- represents the whole display                       --- *)
  o^.x := 0;
  o^.y := 0;

  wimp_open_wind(o);

  (* --- only do a redraw if the size of the window has changed --- *)
  if (old_x <> (o^.box.x1 - o^.box.x0)) or
     (old_y <> (o^.box.y1 - o^.box.y0))
    then begin
           balls64_redraw_window(o^.w);
           old_x := o^.box.x1 - o^.box.x0;
           old_y := o^.box.y1 - o^.box.y0;
         end;
end;  

procedure balls64_leftclickproc(i : wimp_i);

var state : wimp_wstate;
    r : wimp_redrawstr;

begin

  if not displaying
    then begin
           (* --- open the window we created --- *)
           wimpt_noerr(wimp_get_wind_state(displaywin_handle, address(state)));
           state.o.behind := -1;  (* make sure it is opened in front *)
           balls64_open_window(address(state.o));
      
           (* --- force a redraw of the whole window --- *)
           r.w := displaywin_handle;
           r.box.x0 := 0;
           r.box.x1 := balls64_Xtowork(SpriteWidth);
           r.box.y0 := -balls64_Ytowork(SpriteHeight);
           r.box.y1 := 0;
           wimp_force_redraw(address(r));
           displaying := TRUE;
         end;
end;

(************************** THE APPLICATION ITSELF *************************)

procedure balls64_changedbox(flag : integer; cbox : change_box_handle);

begin
  swi('OS_ChangedBox', [0], flag; [1], cbox^);
end;

function rand : integer; extern;

const RAND_MAX = $7fffffff;

function balls64_rnd(v : integer) : integer;

begin
  balls64_rnd := trunc((rand / RAND_MAX) * v) + 1
end;

function balls64_fnx : integer;

begin
  balls64_fnx := balls64_rnd(balls64_Xtowork(SpriteWidth))
end;

function balls64_fny : integer;

begin
  balls64_fny := balls64_rnd(balls64_Ytowork(SpriteHeight))
end;

function balls64_fnrgb : integer;

begin
  balls64_fnrgb := (balls64_rnd(3)-1)*1 + 
                   (balls64_rnd(3)-1)*4 + 
                   (balls64_rnd(3)-1)*16
end;

procedure balls64_do_ball;

var state : sprite_state;
    r : wimp_redrawstr;
    cbox : change_box_ptr;
    l : real;
    t, x : integer;
    base : integer;
    orgx, orgy : integer;

begin

  (* --- redirect VDU output to the sprite saving old state --- *)
  wimpt_complain(sprite_outputtosprite(my_sprite.area, 
                                       address(my_sprite.id),
                                       save_area, 
                                       address(state)));
  (* --- enable checking changes to the "screen" (really our sprite) --- *)
  balls64_Changedbox(1, address(cbox));
  balls64_Changedbox(2, address(cbox));

  orgx := balls64_fnx;
  orgy := balls64_fny;
  l := ln(512/Radius)/ln(2);
  base := balls64_fnrgb;
  x := Radius;
  while x >= Step
    do begin
         t := trunc(l);
         bbc_vduq(23, 17, 2, 512-shl(x, t), 0, 0, 0, 0, 0);
         if x <= RadDiv2
           then bbc_gcol(0, base+$15)
           else bbc_gcol(0,base);
         bbc_move(orgx - x div 3,orgy - x div 3);
         bbc_plot($9D, orgx+x, orgy);
         x := x - Step;
       end;

  (* --- see what's changed on the "screen" (ie. our sprite) --- *)
  balls64_Changedbox(-1, address(cbox));

  r.w := displaywin_handle;
  r.box.x0 := balls64_Xtowork(cbox^.box.x0) * xmagmult div xdivmult
               - balls64_Xtowork(1);
  r.box.x1 := balls64_Xtowork(cbox^.box.x1) * xmagmult div xdivmult
               + balls64_Xtowork(1);
  r.box.y0 := balls64_Ytowork(cbox^.box.y0 - SpriteHeight)
               * ymagmult div ydivmult - balls64_Ytowork(1);
  r.box.y1 := balls64_Ytowork(cbox^.box.y1 - SpriteHeight)
               * ymagmult div ydivmult + balls64_Ytowork(1);
    
  (* --- restore output back to the VDU screen --- *)
  wimpt_complain(sprite_restorestate(state));
  
  (* --- update the window contents --- *)
  balls64_update_window(r);
end;
     
(****************************** EVENT HANDLING *****************************)

var bpp_reported : boolean;

procedure balls64_bpp_warn;

begin
  if not bpp_reported
    then begin
           werr(FALSE, 'Warning: I only look my best in 8-bpp modes');
           bpp_reported := TRUE;
         end;
end;

procedure balls64_handler(e : wimp_eventstr_ptr; handle : pointer);

begin

  case e^.e of
    wimp_ENULL:
      if not frozen and displaying
        then balls64_do_ball;

    wimp_EREDRAW:
      balls64_redraw_window(e^.data.o.w);

    wimp_EOPEN:
      balls64_open_window(address(e^.data.o));

    wimp_ECLOSE:
      begin
        wimpt_noerr(wimp_close_wind(e^.data.o.w));
        displaying := FALSE;
      end;

    wimp_ESEND,
    wimp_ESENDWANTACK:     (* 
                            * this code checks for mode/palette
                            * broadcasts
                            *)
      case e^.data.msg.hdr.action of
        wimp_PALETTECHANGE:
          wimpt_complain(colourtran_select_table(SpriteMode,
                         nil, -1,
                         wimp_paletteword_ptr(-1), address(trans)));

        wimp_MMODECHANGE:
          begin
            wimpt_checkmode;
            if wimpt_bpp <> 8
              then balls64_bpp_warn;
            wimpt_complain(colourtran_select_table(SpriteMode,
                          nil, -1,
                          wimp_paletteword_ptr(-1), address(trans)));
          end;
          

        wimp_MHELPREQUEST:
          begin
            e^.data.msg.hdr.your_ref := e^.data.msg.hdr.my_ref;
            e^.data.msg.hdr.action := wimp_MHELPREPLY;
            e^.data.msg.hdr.size := 256;
            if e^.data.msg.helprequest.m.i = -1 (*ie. not on our icon*)
              then e^.data.msg.helpreply.text :=
                    'This is the balls64 display.|MOnly one can be active'
              else e^.data.msg.helpreply.text :=
                    'This is the balls64 icon.|MClick SELECT to start display';
            wimpt_noerr(wimp_sendmessage(wimp_ESEND, address(e^.data.msg),
                                         e^.data.msg.hdr.task));
          end;
    end;

  end;
end;

procedure balls64_info_aboutprog;

var d : dbox;

begin

  (* --- display info about the program in a dialogue box --- *)
  d := dbox_new('ProgInfo');

  dbox_showstatic(d);

  dbox_fillin(d);

  dbox_dispose(d);
end;

function balls64_saver(filename : string; handle : pointer) : boolean;

var e : os_error;

begin

  (* --- save the sprite area in a sprite file --- *)
  visdelay_begin;
  e := wimpt_complain(sprite_area_save(my_sprite.area, filename));
  visdelay_end;

  balls64_saver := not e;
end;
   
(******************************* MENU HANDLING *****************************)

function balls64_menumaker(handle : pointer) : menu;

var temp : menu;

begin

  (* --- create a menu for the icon on the icon bar --- *)
  temp := menu_new('Balls64', '>Info,>Save,Display,Freeze,Quit');

  (* --- fade out "start" field if we already have balls on display --- *)
  menu_setflags(temp, iconmenu_MDisplay, false, displaying);

  (* --- tick/untick "freeze" appropriately --- *)
  menu_setflags(temp, iconmenu_MFreeze, frozen, false); 

  balls64_menumaker := temp
end;

procedure balls64_menuproc(handle : pointer; hit : event_hitstr_ptr);

begin
  (* --- see which menu entry has been chosen --- *)
  case integer(hit^[0]) of
    iconmenu_MInfo:
      balls64_info_aboutprog;

    iconmenu_MDisplay:
        balls64_leftclickproc(wimp_i(0));

    iconmenu_MSave:
        saveas(SpriteFile, 'BallsDump', SpriteSize,
               balls64_saver, nil, nil, nil);

    iconmenu_MFreeze:
      if (frozen)
        then begin
               event_setmask(uand(event_getmask, unot(wimp_EMNULL)));
               frozen := FALSE;
             end
        else begin           
               event_setmask(uor(event_getmask, wimp_EMNULL));
               frozen := TRUE;
             end;

    iconmenu_MQuit:
        goto 9999;

  end;
end;

(******************************** INITIALISATION ***************************)

procedure balls64_initialise;

begin
  (* --- initialise wimp library modules --- *)
  wimpt_init('balls64');
  res_init('balls64');
  resspr_init;
  flex_init;
  template_init;
  dbox_init;

  (* --- check which mode we are in --- *)
  wimpt_checkmode;
  if (wimpt_bpp <> 8)
    then balls64_bpp_warn;

  (* --- create sprite to be used as output --- *)
  balls64_create_sprite(my_sprite);

  (* --- create a window for display --- *)
  balls64_create_displaywin(displaywin_handle);

  (* --- attach an event handling function to window --- *)
  win_register_event_handler(displaywin_handle, balls64_handler, nil);

  (* --- make the window we just created get delivered null events --- *)
  (* --- and also unknown events (ie. msgs for palette/mode change --- *)
  win_claim_idle_events(displaywin_handle);
  win_claim_unknown_events(displaywin_handle);

  (* --- put our icon on the icon bar --- *)
  baricon('!balls64', integer(resspr_area), balls64_leftclickproc);

  (* --- attach a menu to the icon on the icon bar --- *)
  event_attachmenumaker(win_ICONBAR, balls64_menumaker, balls64_menuproc, nil);

  (* --- read the palette --- *)
  wimpt_complain(colourtran_select_table(SpriteMode,nil,-1,
                           wimp_paletteword_ptr(-1),address(trans)));

  (* --- activate saving of floating point registers on poll --- *)
  wimp_save_fp_state_on_poll;
end;

(******************************* MAIN PROGRAM ******************************)

begin
  old_x := 0;
  old_y := 0;
  displaying := false;
  frozen := false;
  bpp_reported := false;
  (* --- initialise the environment --- *)
  balls64_initialise;

  (* --- mask off the events we're not interested in --- *)
  event_setmask(uor(wimp_EMPTRENTER, wimp_EMPTRLEAVE));

  (* --- the main event loop --- *)   
  while(TRUE)
    do event_process;
  9999:;
end.
