// MenuBox.bcpl -- a BCPL package to define and manipulate areas
//		on the display screen.
//		A box is defined by an "origin" (upper left corner) and
//		a "corner" (lower right corner).  If a bitmap exists
//		for the box then a dcb is specified and the origin
//		and corner are given relative to the dcb bitmap.

get "MenuDefs.d"


external		// incoming OS statics and procedures
	[
	dsp
	sysZone
	Allocate
	GetFont
	CharWidth
	]



let CreateBox(Xo,Yo,Xc,Yc,inputZone;numargs na) = valof
	[
	// if corners not specified then go home
	if na ls 4 then resultis false

	// use input zone or sysZone
	let zone= ((na ls 5) % (inputZone eq 0)) ? sysZone,inputZone
	let box=Allocate(zone,lBOX)
	if box eq 0 then resultis false

	// set default to no dcb and input absolute coords
	box>>BOX.dcb=0
	box>>BOX.outline=0
	box>>BOX.xorigin=Xo
	box>>BOX.yorigin=Yo
	box>>BOX.xcorner=Xc
	box>>BOX.ycorner=Yc
	if Xo eq Xc % Yo eq Yc then resultis false

	// see if a dcb exists and change coords to relative to dcb
	ConvertToRelative(box)
	resultis box

	]



and CursorInside(box,XCursor,YCursor;numargs na) = valof
	[
	// default cursor location to center of cursor
	if (na eq 0) % (box eq 0) then resultis false
	if na ls 3 then [ XCursor=0 ; YCursor=0 ]
	XCursor=@#424+XCursor
	YCursor=@#425+YCursor

	// define box coords
	let Xo=box>>BOX.xorigin
	let Xc=box>>BOX.xcorner
	let Yo=box>>BOX.yorigin
	let Yc=box>>BOX.ycorner
	if Xo eq Xc % Yo eq Yc then resultis false

	// if dcb was specified, find absolute coords from dcb chain
	let top=FindDCB(box)		// returns # lines to the dcb
	if top eq true then resultis false		// couldn't find the dcb
	let left=top ? 16*((box>>BOX.dcb)>>DCB.indentation),0
	Xo=Xo+left
	Yo=Yo+top
	Xc=Xc+left
	Yc=Yc+top

	// compare cursor coords to absolute coords
	if (XCursor ge Xo) & (XCursor le Xc) then
		[
		if (YCursor ge Yo) & (YCursor le Yc) then resultis true
		]
	resultis false

	]


and OutlineBox(box,bits,flag;numargs na) = valof
	[
	// bits      --  width of outline in bits
	// flag=0   --  outline with zeroes (white in normal mode)
	// flag=1   --  outline with ones (black in normal mode)
	// flag=-1  --  outline by flipping memory

	// set defaults
	if (na eq 0) % (box eq 0) then resultis false
	if na ls 2 then bits=1
	if bits eq 0 then [ box>>BOX.outline=0 ; resultis true ]
	if na ls 3 then flag=-1

	// define boundaries of the box
	let Xo=box>>BOX.xorigin
	let Yo=box>>BOX.yorigin
	let Xc=box>>BOX.xcorner
	let Yc=box>>BOX.ycorner
	if Xo eq Xc % Yo eq Yc then resultis false

	// get dcb
	let dcb=box>>BOX.dcb
	if dcb eq 0 then resultis false
	let width=dcb>>DCB.width

	// draw in top border
	// using erase function from BoxUtils.asm
	// erase(nbits,wordstart,bitstart,nwords,nlines,flag [0])
	let nbits=Xc-Xo+1
	let wordstart=dcb>>DCB.bitmap+Yo*width
	erase(nbits,wordstart,Xo,width,bits,flag)

	// draw in left and right sides
	let nlines=Yc-Yo+1-2*bits
	unless nlines le 0 do
		[
		// draw in left side
		erase(bits,wordstart+bits*width,Xo,width,nlines,flag)
		// draw in right side
		erase(-bits,wordstart+bits*width,Xc,width,nlines,flag)
		]

	// draw in bottom border
	wordstart=dcb>>DCB.bitmap+(Yc-bits+1)*width
	erase(-nbits,wordstart,Xc,width,bits,flag)

	// put in how outlined
	box>>BOX.flag=flag
	box>>BOX.bits=bits
	resultis true

	]


and FlipBox(box,flag;numargs na) = valof
	[
	// check arguments
	if (na eq 0) % (box eq 0) then resultis false
	if na ls 2 then flag=true

	// get dcb
	let dcb=box>>BOX.dcb
	if dcb eq 0 then resultis false
	let width=dcb>>DCB.width

	// define boundaries of the box
	let bits=box>>BOX.bits
	let Xo=box>>BOX.xorigin
	let Yo=box>>BOX.yorigin
	let Xc=box>>BOX.xcorner
	let Yc=box>>BOX.ycorner
	if Xo eq Xc % Yo eq Yc then resultis false

	// flip the box
	let nbits=Xc-Xo+1-2*bits	
	let wordstart=dcb>>DCB.bitmap+(Yo+bits)*width
	let nlines=Yc-Yo+1-2*bits
	if (nbits le 0) % (nlines le 0) then resultis false
	erase(nbits,wordstart,Xo+bits,width,nlines,flag)
	resultis true

	]


and NearestBox(menu;numargs na) = valof
	[
	// returns number of the box that is
	// geometrically closest to the cursor

	// return if conditions wrong
	if (na eq 0) % (menu eq 0) % (@menu eq 0) resultis false

	// define some needed variables
	let distance=nil
	let number=1
	let mindist=DistanceToBox(menu!number)

	// major loop
	for n=1 to @menu-1 do
		[
		distance=DistanceToBox(menu!(n+1))
		if distance ls mindist then
			[
			mindist=distance
			number=n+1
			]
		]
	resultis number
	]


and DistanceToBox(box) = valof
	[
	// calculates delta x and delta y
	// if delta is > 127 then divide by 8 and square
	// if delta is < 128 then square and divide by 64
	// this insures that there is no overflow
	// while maintaining good accuracy for small distances
	// returns the sum of the squares of the distances
	let dx=(@#424-box>>BOX.xcorner)
	let dy=(@#425-box>>BOX.ycorner)
	if dx ls 0 then dx=-dx
	if dy ls 0 then dy=-dy
	let s1,s2=0,0
	test dx ls 128 ifso s1=s1+dx*dx ifnot [ dx=dx/8 ; s2=s2+dx*dx ]
	test dy ls 128 ifso s1=s1+dy*dy ifnot [ dy=dy/8 ; s2=s2+dy*dy ]
	s1=s1/64
	resultis s1+s2
	]


and FindDCB(box,dcb;numargs na) = valof
	[
	// return number of lines to top of dcb for box
	// return false if dcb entry eq 0
	// return true if dcb supposed to be but not there

	// check if no dcb
	if (na eq 0) % (box>>BOX.dcb eq 0) then resultis false
	if (na le 1) % (dcb eq 0) then dcb=@#420

	// look for dcb and count lines
	let top=0
	while dcb do
		[
		if dcb eq box>>BOX.dcb then resultis top
		top=top+2*(dcb>>DCB.height)
		dcb=@dcb
		]

	resultis true
	]


and ConvertToRelative(box,dcb;numargs na) be
	[
	// check if no dcb
	if (na eq 0) % (box eq 0) then return
	if (na le 1) % (dcb eq 0) then dcb=@#420
	if box>>BOX.dcb then return

	// run over chain
	// see if a dcb exists and change coords to relative to dcb
	let top,height,left,width=0,0,0,0
	let Xo=box>>BOX.xorigin
	let Yo=box>>BOX.yorigin
	let Xc=box>>BOX.xcorner
	let Yc=box>>BOX.ycorner
	if Xo eq Xc % Yo eq Yc then return
	while dcb do
		[
		height=2*(dcb>>DCB.height)
		if top gr Yo then return
		if top+height gr Yc then
			[
			left=16*(dcb>>DCB.indentation)
			width=16*(dcb>>DCB.width)
			if (left le Xo) & (left+width gr Xc) then
				[
				box>>BOX.dcb=dcb
				box>>BOX.xorigin=Xo-left
				box>>BOX.yorigin=Yo-top
				box>>BOX.xcorner=Xc-left
				box>>BOX.ycorner=Yc-top
				]
			return
			]
		top=top+height
		dcb=@dcb
		]

	]

and WriteBox(box,string,font;numargs na) be
	[
	// Jiggered up for left justified
	// set defaults
	if (na ls 1) % (box eq 0) then return
	if (na ls 2) % (string eq 0) then return
	if (na ls 3) % (font eq 0) then font=GetFont(dsp)

	// calculate the bit length of the string
	let length=0
	for n=1 to string>>STRING.length do
		[
		length=length+CharWidth(font,string>>STRING.char↑n)
		]

	// get coordinates
	let Xo=box>>BOX.xorigin
	let Yo=box>>BOX.yorigin
	let Xc=box>>BOX.xcorner
	let Yc=box>>BOX.ycorner
	if Xo eq Xc % Yo eq Yc then return

	// calculate where the string goes
	let charheight=font!(-2)
	let bits=box>>BOX.bits
	let width=Xc-Xo+1-2*bits
	let height=Yc-Yo+1-2*bits
	let xstart=Xo+(width-length)/2+bits
	if xstart ls Xo then xstart=Xo+bits
	if box>>BOX.ljustified ne 0 then xstart = Xo+bits+box>>BOX.joffset
	let ystart=Yo+(height-charheight)/2+bits
	if ystart ls Yo then ystart=Yo+bits


	// now put it out
	// write(StringPointer,nwrds,dba,wad,bitlimit,FontPointer)
	let dcb=box>>BOX.dcb
	let wordstart=dcb>>DCB.bitmap+ystart*(dcb>>DCB.width)
	write(string,dcb>>DCB.width,xstart+1,wordstart,width,font)
	return
	]