;; This example demonstrates hardware scrolling using the CRTC.
;;
;; This example will function on CPC, CPC+ and KC Compact and ALL CRTC
;; variants.
;;
;; The rate of horizontal scroll is a minimum of 2 CRTC characters.
;; This is equivalent to:
;; - 4 pixels in mode 0
;; - 8 pixels in mode 1
;; - 16 pixels in mode 2
;;
;; The rate of vertical scroll is a minimum of a CRTC character line (the number
;; of scanlines in the CRTC line is defined using register 9 of the CRTC).
;; With 8 scanlines per CRTC character line this is equivalent to 8 scanlines.
;;
;; The horizontal scroll rate can be improved to scroll by:
;; This is equivalent to:
;; - 2 pixels in mode 0
;; - 4 pixels in mode 1
;; - 8 pixels in mode 2
;;
;; Using register 3 of the CRTC. Another example will demonstrate this method.
;;
;; The vertical scroll rate can be improved to scroll by a single scanline at a time,
;; by using register 5 of the CRTC. Another example will demonstrate this method.
;;
;; It is very difficult to scroll the screen pixel by pixel using hardware alone
;; on the standard CPC hardware. If you want pixel scrolling using the hardware,
;; then please use the CPC+ enhancements.
;;
;; Hardware scrolling is made by changing the start address
;; of the display.
;;
;; Use the following keys:
;; Q - scroll screen up
;; A - scroll screen down
;; O - scroll screen to left
;; P - scroll screen to right

;; NOTE position of this code is not important
org &4000
nolist

.km_read_key equ &bb1b
.mc_wait_flyback equ &bd19
.scr_set_mode equ &bc0e
.txt_output	equ &bb5a

;; set the screen mode
ld a,1
call scr_set_mode

;; display a message (so we can see how the screen is scrolling; by the movement
;; of the message)
ld hl,message
call display_message

.loop
;; wait for vsync 
;;
;; (synchronises scroll with the screen refresh; as a result it
;; updates at a constant rate and is smooth)
call mc_wait_flyback

;; check if any keys are pressed (they will update the scroll offset)
call check_keys

;; set the scroll offset
ld hl,(scroll_offset)
call set_offset

;; loop
jp loop

;;----------------------------------------------------------------------
.check_keys
;; test if any key has been pressed
call km_read_key
ret nc
;; A = code of the key that has been pressed
;;
;; check the codes we are using and handle appropiatly.
cp 'Q'				; Q
jp z,scroll_up
cp 'A'				; A
jp z,scroll_down
cp 'O'				; O
jp z,scroll_left
cp 'P'				; P
jp z,scroll_right
ret


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

.scroll_up
;; get the current scroll offset
ld hl,(scroll_offset)

;; update it
;;
;; NOTE: 
;; - 40 is the number of CRTC characters in a CRTC character row 
;; and it is identical to the value written to CRTC register 1 (Horizontal Displayed).
;;
;; - If you change the width of the display (using register 1), then you must
;; change this to the same value for the scroll to operate correctly.

ld bc,40
or a
sbc hl,bc


;; ensure scroll offset is in range &000-&3ff
ld a,h
and &3
ld h,a

;; store new scroll offset. It is now ready to be written to the CRTC.
ld (scroll_offset),hl
ret

;;----------------------------------------------------------------------
.scroll_down
;; get current scroll offset
ld hl,(scroll_offset)

;; update it
;;
;; NOTE: 
;; - 40 is the number of CRTC characters in a CRTC character row 
;; and it is identical to the value written to CRTC register 1 (Horizontal Displayed).
;;
;; - If you change the width of the display (using register 1), then you must
;; change this to the same value for the scroll to operate correctly.
ld bc,40
add hl,bc

;; ensure scroll offset is in range &000-&3ff
ld a,h
and &3
ld h,a

;; store new scroll offset. It is now ready to be written to the CRTC.
ld (scroll_offset),hl
ret

;;----------------------------------------------------------------------
.scroll_left
;; get current scroll offset
ld hl,(scroll_offset)

;; update it
dec hl

;; ensure scroll offset is in range &000-&3ff
ld a,h
and &3
ld h,a

;; store new scroll offset. It is now ready to be written to the CRTC.
ld (scroll_offset),hl
ret

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

.scroll_right
;; get current scroll offset
ld hl,(scroll_offset)

;; update it
inc hl

;; ensure scroll offset is in range &000-&3ff
ld a,h
and &3
ld h,a

;; store new scroll offset. It is now ready to be written to the CRTC.
ld (scroll_offset),hl
ret

;;----------------------------------------------------------------------
;;
;; HL = scroll offset in CRTC character units

.set_offset
ld bc,&bc0c				;; select CRTC register 12
out (c),c
ld b,&bd				;; B = I/O address for CRTC register write

;; combine with scroll base
ld a,(scroll_base)
or h
out (c),a

ld bc,&bc0d				;; select CRTC register 13
out (c),c
ld b,&bd				;; B = I/O address for CRTC register write
out (c),l
ret

;;----------------------------------------------------------------------
;; HL = points to start of message
;; message is terminated with a 0 character

.display_message
ld a,(hl)			;; get character
inc hl				;; increment message pointer
or a				;; termination character?
ret z
call txt_output		;; display character
jr display_message	;; loop

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

;; high byte of the screen base address
;; &00 -> screen uses &0000-&3fff
;; &10 -> screen uses &4000-&7fff
;; &20 -> screen uses &8000-&bfff
;; &30 -> screen uses &c000-&ffff

.scroll_base defb &30

;; the scroll offset in CRTC character units
.scroll_offset defw 0

;;----------------------------------------------------------------------
.message defb "Lets scroll!!",0