
VERSIONSTRING equ "Gfx v2.05"

!
! Filename:     gfx.asm
! Module name:  Gfx
! Description:  standard mode video driver
!
! Commands:
!   string QueryVersion () - query version string
!   SetMode (byte onoff) - turn standard mode on or off
!   Clear () - clear the screen
!   Dump (byte[30] bitmap) - write bitmap to display
!   DumpResource (byte rezID) - write bitmap resource to display
!   Shift (byte how) - shift or scroll the display one notch
!   ScrollStop () - stop auto-scrolling display
!   ScrollSpeed (byte speed) - set scroll speed
!   ScrollStart (byte how, word duration) - start auto-scrolling display
!   ScrollCstring (cstring string) - scroll C string
!   ScrollPstring (pstring string) - scroll Pascal string
!   ScrollStringResource (byte rezID) - scroll resource string
!   UseFontResource (byte rezID) - use a font
!   SetScrollRange (byte firstcolumn, byte length) - set scroll range
!
! IPC:
!   IPC_Gfx_SetModeOff
!
! Timers:
!   TIMER_Gfx - timer for scrolling display
!
! Hooks:
!   HOOK_GFX - video refresh hook
!
! Comments:
!   none
!
! Hardware notes:
!   To set the currently active column, write the column number to
!   PORT_Gfx_Column.  To set which lights are lit in that column,
!   write to PORT_Gfx_Data, active high, bit 0 is at the top.
!
! History:
!   bv 9/12/98	- initial revision
!   bv 5/30/99	- converted to CokeOS v3 module
!   bv 6/16/99	- updated for new response style
!   bv 6/22/99 	- updated for new hook style, added scroll range
!   bv 7/16/99	- updated for new IPC style
!   bv 7/25/99	- added IPC_Gfx_SetModeOff
!   bv 10/3/99	- allowed ScrollStringResource to scroll more than 256 chars 
	
	include <coke/coke.inc>

; constants

GFX_REFRESHDELAY equ 64	; 250 KHz / 64 / 30 = 120 Hz screen refresh	
GFX_DEFAULTSPEED equ 4

; EntryPoint

EntryPoint subroutine
	cmpa	#IPC_Coke_Hello
	beq	.init
	cmpa	#IPC_Coke_Goodbye
	beq	.free
	cmpa	#IPC_Gfx_SetModeOff
	beq	.off
	sec
	rts
.init
	ldx	#CommandTable			; install command table
	jsr	Coke_InstallCommandTable
	ldaa	#ID_Gfx				; install IPC
	ldx	#EntryPoint
	jsr	Coke_RegisterIPC
	ldd	#GFX_DEFAULTSPEED		; set default scroll speed
	std	video_scrollspeed16
	clr	video_firstcolumn		; init scroll range
	ldaa	#29
	staa	video_lastcolumn
	inca	
	staa	video_length
	clr	video_numchars			; no font yet
	ldd	#video_buffer			; init cursor pos 
	std	video_cursorpos
	clr	video_on			; not active yet
	clc
	rts
.free
	ldx	#CommandTable			; remove command table
	jsr	Coke_RemoveCommandTable
	ldaa	#ID_Gfx				; remove IPC
	jsr	Coke_UnregisterIPC
	ldaa	#TIMER_Gfx			; remove timer
	jsr	Coke_RemoveTimer
	tst	video_on			; is the mode active?
	beq	.noton
	ldx	#RefreshHookEnd			; if so, remove the hook
	ldaa	#HOOK_GFX
	jsr	Coke_RemoveHook
.noton
	clc
	rts
.off
	psha
	pshx
	tst	video_on		; is it already off?
	beq	.offnoton		; if so, ignore
	clr	video_on		; if not, clear it
	ldaa	#HOOK_GFX
	ldx	#RefreshHookEnd
	jsr	Coke_RemoveHook		; remove the hook function
.offnoton
	pulx
	pula
	clc
	rts


; SetCursor sets the cursor position for subsequent
; drawing operations.
; horizontal cursor position (from zero) is in b.

SetCursor subroutine
	pshx
	ldx	#video_buffer	; get start of video buffer
	abx			; add cursor pos
	stx	video_cursorpos	; store it
	pulx
	rts


; ShiftLeft shifts the screen to the left, without wrap-around.
; The right-most column is zeroed.

ShiftLeft	subroutine
	psha
	pshb	
	pshx

	ldx	#video_buffer
	ldab	video_firstcolumn
	abx				; set x to start of scroll range
	ldab	video_length		; get number of lines to do
	decb
	beq	.done
.shiftloop
	ldaa	1,x			; get column
	staa	0,x			; move it left
	inx				; increment x
	decb				; dec counter 
	bne	.shiftloop
	clr	0,x			; clear rightmost column
.done
	pulx
	pulb	
	pula
	rts


; ShiftRight shifts the screen to the right, without wrap-around.
; The left-most column is zeroed.

ShiftRight	subroutine
	psha
	pshb
	pshx

	ldx	#video_buffer
	ldab	video_lastcolumn
	decb
	abx			; set x to next to end of scroll range
	ldab	video_length	; get number of lines to do
	decb
	beq	.done
.shiftloop
	ldaa	0,x		; get column
	staa	1,x		; move it right
	dex			; dec x
	decb			; dec counter
	bne	.shiftloop
	clr	1,x		; clear leftmost column
.done
	pulx
	pulb	
	pula
	rts


; ScrollLeft scrolls the screen left, with wrap-around.

ScrollLeft	subroutine
	psha
	pshb
	pshx

	ldx	#video_buffer
	ldab	video_firstcolumn
	abx				; set x to start of scroll range
	ldaa	0,x			; get the line
	jsr	ShiftLeft		; do the shift
	ldab	video_length		; go to the end of the scroll range
	decb
	abx
	staa	0,x			; store the line	

	pulx
	pulb
	pula
	rts


; ScrollRight scrolls the screen right, with wrap-around.

ScrollRight subroutine
	psha
	pshb
	pshx

	ldx	#video_buffer
	ldab	video_lastcolumn
	abx				; set x to end of scroll range
	ldaa	0,x			; get the line
	jsr	ShiftRight		; do the shift
	ldx	#video_buffer
	ldab	video_firstcolumn
	abx				; set x to start of scroll range
	staa	0,x			; store the line	

	pulx
	pulb
	pula
	rts


; DoShift does a shift. 
; how code in a

DoShift subroutine
	cmpa	#SHIFT_ShiftLeft
	beq	.shl
	cmpa	#SHIFT_ShiftRight
	beq	.shr
	cmpa	#SHIFT_ScrollLeft
	beq	.scrl
	cmpa	#SHIFT_ScrollRight
	beq	.scrr
	rts
.shl
	jsr	ShiftLeft
	rts
.scrl
	jsr	ScrollLeft
	rts
.shr	
	jsr	ShiftRight
	rts
.scrr	
	jsr	ScrollRight
	rts


; UseChar resets the char line pointer to the start of the
; requested char.  If the char doesn't exist, a space is drawn.
; char to use is in b.

UseChar	subroutine
	pshx
	pshb
	psha

	cmpb	video_numchars	; does the char exist?
	bhs	.badchar	; if so, put in a space
	clra			; clear high byte
	asld			; multiply by two
	addd	video_fontptr	; add offset to start of table
	xgdx			; put into x
	ldd	0,x		; get pointer to start of char
.putptr
	std	video_charptr	; store the pointer
.exit
	pula
	pulb
	pulx
	rts
.badchar
	ldd	#.dummychar
	bra	.putptr

.dummychar
	dc.b	$80


; DrawCharLine draws one line of the char at the cursor position,
; and updates the pointer (but does NOT change the cursor position!)
; If it draws the final line of the char, carry is returned set.
; Otherwise, carry is clear.

DrawCharLine subroutine
	pshx
	psha
	
	ldx	video_charptr	; get the pointer to the line
	clc
	ldaa	0,x		; get the goods
	bpl	.notlast	; if high bit is set, that's the last char
	anda	#$7f		; if so, remove high bit
	sec			; and set carry for return
.notlast
	inx			; update the char ptr
	stx	video_charptr	; store it
	ldx	video_cursorpos	; get the cursor position
	staa	0,x		; store the line

	pula
	pulx
	rts
	

; DrawChar draws an entire char at the cursor position.
; It doesn't check for drawing outside the buffer.
; char to use is in b

DrawChar	subroutine
	jsr	UseChar		; use the char
.drawloop
	jsr	DrawCharLine	; draw a line
	ldx	video_cursorpos	; increment the cursor position
	inx
	stx	video_cursorpos
	bcc	.drawloop	; if not the end, go again.
	rts			; otherwise, go home

	

; ScrollReset sets the pointers to the start and end of the buffer, and
; gets a head start on setting up the char.

ScrollReset subroutine
	pshb
	pshx

	ldx	#string_buffer		; get the buf ptr
	stx	string_ptr		; put it there
	ldab	0,x			; get the first char
	jsr	UseChar			; set it up
	ldab	string_length		; get the string length
	abx				; add it to the ptr
	stx	string_scrollend	; store it as the place to end
	ldab	video_lastcolumn	; set cursor to right side of range
	jsr	SetCursor

	pulx
	pulb
	rts


; ScrollMem is like ScrollReset, but it takes a pointer to the
; start of a string in memory, and prepares to scroll that instead.
; The string can be more than 256 chars.
; pointer to string in x, length in d.

ScrollMem subroutine
	psha
	pshb

	stx	string_ptr		; put x as the string ptr
	addd	string_ptr		; add the length
	std	string_scrollend	; that's the ending ptr

	ldab	0,x			; get the first char of the string
	jsr	UseChar			; set it up

	ldab	video_lastcolumn	; set cursor to right side of display
	jsr	SetCursor

	pulb
	pula
	rts


; ScrollString shifts the screen left and fills in the right-most column.
; When it finishes with the last character, it returns with carry set.
; Otherwise, carry is clear.

ScrollString
	pshb
	pshx

	ldx	string_ptr		; get the current ptr
	cpx	string_scrollend	; is there nothing to do?
	blo	.notdone		; if not, continue
.nomore
	sec				; if so, set carry
	pulx
	pulb
	rts				; and leave

.notdone
	jsr	ShiftLeft		; shift the screen left
	jsr 	DrawCharLine		; draw a line (assume cursor is set)
	bcc	.goaway			; if not the last line, go home
	inx				; if char is done, inc string ptr
	stx	string_ptr		; store it
	cpx	string_scrollend	; is it the end?
	bhs	.nomore			; if so, return with carry set
	ldab	0,x			; if not, get the new character
	jsr	UseChar			; tell the driver to use it
.goaway
	clc				; good exit
	pulx
	pulb
	rts	



; Command_SetMode (byte onoff)

Command_SetMode subroutine
	jsr	Coke_GetChar		; get onoff
	tsta				; turning on or off?
	beq	.off
	
	tst	video_on		; is it already on?
	bne	.done			; if so, ignore

	ldaa	#IPC_Gfx_SetModeOff	; turn off all other graphics modes
	ldab	#IPCFLAG_Broadcast	; send to all
	jsr	Coke_CallIPC

	ldaa	#1
	staa	video_on		; turn us on, baby!
	clr	video_column		; clear the column
	ldd	#GFX_REFRESHDELAY	; get the standard mode refresh rate
	std	gfxdelay		; put it in the system variable
	ldaa	#HOOK_GFX
	ldx	#RefreshHookEnd
	jsr	Coke_InstallHook	; install the hook function
	bra	.done
.off
	tst	video_on		; is it already off?
	beq	.done			; if so, ignore
	clr	video_on		; if not, clear it
	ldaa	#HOOK_GFX
	ldx	#RefreshHookEnd
	jsr	Coke_RemoveHook		; remove the hook function

.done
	ldaa	#COKE_ACK
	rts


; Command_Clear ()

Command_Clear	subroutine
	clra				; clear out a
	ldab	#30
	ldx	#video_buffer+29	; go to end of buffer
.clearloop
	staa	0,x			; store a zero
	dex				; decrement
	decb				; dec counter
	bne	.clearloop		; if not done, keep going

	ldaa	#COKE_ACK
	rts	


; Command_Dump (30 bytes)

Command_Dump subroutine
	ldx	#video_buffer		; get pointer to video buffer
	ldab	#30			; do thirty lines
.loop
	jsr	Coke_GetChar		; get the data
	staa	0,x			; store it in the buffer
	inx				; next line
	decb				; dec counter
	bne	.loop			; if not done, loop

	ldaa	#COKE_ACK
	rts


; Command_DumpResource (byte rezid)

Command_DumpResource subroutine
	jsr	Coke_GetChar		; get rez id
	tab				; put into b
	jsr	Resource_GetResource	; get the resource ptr
	bcs	.nope			; if bad resource ptr, then abort
	
	ldy	#video_buffer		; get ptr to buf
	ldab	#30			; do 30 bytes
.loop
	ldaa	0,x			; get byte from resource file
	staa	0,y			; put it into buffer
	inx				; next byte
	iny	
	decb				; dec counter
	bne	.loop			; if not done, loop
.nope
	ldaa	#COKE_ACK
	rts


; Command_Shift (byte how)

Command_Shift subroutine
	jsr	Coke_GetChar		; get how byte
	jsr	DoShift			; do it
	
	ldaa	#COKE_ACK
	rts


; Command_ScrollStop ()

Command_ScrollStop subroutine
	ldaa	#TIMER_Gfx		; get our timer id
	jsr	Coke_RemoveTimer	; kill the timer

	ldaa	#COKE_ACK
	rts


; Command_ScrollSpeed (byte speed)

Command_ScrollSpeed subroutine
	jsr	Coke_GetChar		; get the param
	staa	video_scrollspeed	; store it
	
	ldaa	#COKE_ACK
	rts


; Command_ScrollStart (byte how, word duration)

Command_ScrollStart subroutine
	jsr	Coke_GetChar		; get the param
	staa	video_scrollhow		; store it
	
	jsr	Coke_GetChar		; get duration
	tab
	jsr	Coke_GetChar
	std	video_scrollduration	; store it

	ldaa	#TIMER_Gfx
	ldx	#ScrollTimerFunction
	ldy	video_scrollspeed16	
	jsr	Coke_SetTimer		; set the timer

	ldaa	#COKE_ACK
	rts


; Command_ScrollCstring (null-terminated string)

Command_ScrollCstring subroutine
	ldx	#string_buffer		; x at the start of the buffer

	clrb				; b is current length
.getloop
	jsr	Coke_GetChar		; get a char
	tsta				; is it zero?
	beq	.endloop		; if so, time to go

	staa	0,x			; if not, write to the buffer
	inx				; inc the buffer ptr
	cmpb	#$ff			; is it max length?
	beq	.endloop		; if so, jump outa here
	incb				; inc the length
	bra	.getloop		; keep going
.endloop
	stab	string_length		; set the new string length
	tstb				; is b zero?
	beq	.nope			; if so, do nothing

	jsr	ScrollReset		; set the scrolly thing to the string
	
	ldaa	#TIMER_Gfx		; get timer id
	ldx	#StringTimerFunction	; get ptr to timer proc
	ldy	video_scrollspeed16	; get speed
	jsr	Coke_SetTimer		; do the timer
.nope
	ldaa	#COKE_ACK
	rts


; Command_ScrollPstring (byte length) (string)

Command_ScrollPstring subroutine
	ldx	#string_buffer		; get buffer ptr
	jsr	Coke_GetChar		; get length
	staa	string_length		; store it
	tab				; put in b for counting
	beq	.nope			; if zero, ignore
.getloop	
	jsr	Coke_GetChar		; get char
	staa	0,x			; store it in buf
	inx				; inc ptr
	decb				; dec counter
	bne	.getloop		; loop

	jsr	ScrollReset		; set the scrolly thing to the string
	
	ldaa	#TIMER_Gfx		; get timer id
	ldx	#StringTimerFunction	; get ptr to timer proc
	ldy	video_scrollspeed16	; get speed
	jsr	Coke_SetTimer		; do the timer
.nope
	ldaa	#COKE_ACK
	rts


; Command_ScrollStringResource (byte rez id)

Command_ScrollStringResource subroutine
	jsr	Coke_GetChar		; get rez id
	tab				; put in b
	jsr	Resource_GetResource	; get rez ptr
	bcs	.done			; if invalid rez, get outa here
	jsr	Resource_GetLength	; get size in d
	tstb				; is the length zero?
	bne	.doit
	tsta
	beq	.done
.doit
	jsr	ScrollMem		; set scrolly thing to mem thing

	ldaa	#TIMER_Gfx		; get timer id
	ldx	#StringTimerFunction	; get ptr to timer proc
	ldy	video_scrollspeed16	; get speed
	jsr	Coke_SetTimer		; do the timer
.done
	ldaa	#COKE_ACK
	rts


; Command_UseFontResource (rez id)

Command_UseFontResource subroutine
	jsr	Coke_GetChar		; get rez id
	tab				; put in b
	jsr	Resource_GetResource	; get the rez ptr
	bcs	.badresid		; if bad resource id, fail
	ldaa	0,x			; get num chars
	staa	video_numchars		; store it
	stx	video_scratch		; store top of file
	inx				; go to start of table
	stx	video_fontptr		; store it
	tsta				; any chars?
	beq	.done			; if not, we're done
	psha				; stash num chars
	jsr	Resource_GetAttributes	; get attributes byte
	bita	#RFLAG_INITIALIZED	; has it been relocated?
	bne	.alreadyinit		; if relocated, we're done
	oraa	#RFLAG_INITIALIZED	; if not, set the flag
	jsr	Resource_SetAttributes
	pula				; get back num chars
.loop
	ldab	1,x			; get lobyte of char offset
	addb	video_scratch+1		; add loybyte of resource ptr
	stab	1,x			; store lobyte
	ldab	0,x			; get hibyte of char offset
	adcb	video_scratch		; add hibyte of rez ptr (with carry)
	stab	0,x			; store hibyte

	inx				; next table entry
	inx
	deca				; dec counter
	bne	.loop
	bra	.done

.alreadyinit
	pula
.done
	ldaa	#COKE_ACK
	rts

.badresid
	ldab	#ERROR_Coke_BadResourceID
	ldaa	#COKE_FAIL
	rts


; Command_SetScrollRange (byte firstcolumn, byte length)

Command_SetScrollRange subroutine
	jsr	Coke_GetChar		; get firstcolumn
	cmpa	#30			; is it too big?
	bhs	.nofirstcol		; if so, don't use it
	staa	video_firstcolumn	; if not, store it
.nofirstcol
	jsr	Coke_GetChar		; get length
	tsta				; is it zero length?
	beq	.bad			; if so, use full screen.
	adda	video_firstcolumn	; add firstcolumn
	cmpa	#30			; is it too big?
	bls	.nottoobig		; if not, use it
.bad
	ldaa	#30			; if so, go to end
.nottoobig
	staa	video_lastcolumn	; store it
	dec	video_lastcolumn	; adjust last column
	suba	video_firstcolumn	; subtract first column
	staa	video_length		; get length back and store it

	ldaa	#COKE_ACK
	rts


; ScrollTimerFunction scrolls the display or something.

ScrollTimerFunction subroutine
	psha
	pshx

	ldaa	video_scrollhow		; get the how code
	jsr	DoShift			; do something with it

	ldx	video_scrollduration	; get duration
	beq	.done			; if zero, go forever
	dex				; dec
	stx	video_scrollduration 	; store it
	bne	.done			; if zero now, stop timer
	ldaa	#TIMER_Gfx
	jsr	Coke_RemoveTimer
.done
	pulx
	pula
	rts	


; StringTimerFunction scrolls the string across the display

StringTimerFunction subroutine
	jsr	ScrollString		; do it
	bcc	.done			; was that the end?
	
	psha
	ldaa	#TIMER_Gfx		; if so, kill the timer
	jsr	Coke_RemoveTimer
	pula
.done
	rts	


; RefreshHookFunction is called during an interrupt proc to multiplex the
; display.  The "asl"s compensate for the hardware flaw.

RefreshHookFunction subroutine
	ldab	video_column	; get the current column number
	decb			; decrement
	bpl	.nocolwrap	; did it go negative?
	ldab	#29		; if so, go to the end (of the physical screen)
.nocolwrap
	stab	video_column	; store it in the variable
	tba
	ldx	#video_buffer	; get offset to video buffer
	abx			; and add column number
	asla			; compensate for hardware flaw
	ldab	0,x		; get the data in the buffer
	aslb			; compensate for hardware flaw
	staa	PORT_Gfx_Column	; also set the display to that column
	stab	PORT_Gfx_Data	; send the data out to the display

RefreshHookEnd
	_endhook RefreshHookFunction


; version stuff

	_versionstuff

; Command table

CommandTable subroutine
	dc.b	CMD_Gfx_QueryVersion
	dc.w	Command_QueryVersion
	dc.b	CMD_Gfx_SetMode
	dc.w	Command_SetMode
	dc.b	CMD_Gfx_Clear
	dc.w	Command_Clear
	dc.b	CMD_Gfx_Dump
	dc.w	Command_Dump
	dc.b	CMD_Gfx_DumpResource
	dc.w	Command_DumpResource
	dc.b	CMD_Gfx_Shift
	dc.w	Command_Shift
	dc.b	CMD_Gfx_ScrollStop
	dc.w	Command_ScrollStop
	dc.b	CMD_Gfx_ScrollSpeed
	dc.w	Command_ScrollSpeed
	dc.b	CMD_Gfx_ScrollStart
	dc.w	Command_ScrollStart
	dc.b	CMD_Gfx_ScrollCstring
	dc.w	Command_ScrollCstring
	dc.b	CMD_Gfx_ScrollPstring
	dc.w	Command_ScrollPstring
	dc.b	CMD_Gfx_ScrollStringResource
	dc.w	Command_ScrollStringResource
	dc.b	CMD_Gfx_UseFontResource
	dc.w	Command_UseFontResource
	dc.b	CMD_Gfx_SetScrollRange
	dc.w	Command_SetScrollRange

	dc.b	0



; data segment

video_buffer		ds 30
video_cursorpos		ds 2
video_column		ds 1
video_on		ds 1
video_charptr		ds 2
video_scrollspeed16	ds 1
video_scrollspeed	ds 1
video_scrollduration	ds 2
video_scrollhow		ds 1
video_fontptr		ds 2
video_numchars		ds 1
video_firstcolumn	ds 1
video_lastcolumn	ds 1
video_length		ds 1
video_scratch		ds 2

string_buffer		ds 256
string_length		ds 1
string_ptr		ds 2
string_scrollend 	ds 2
