; ****************************************************************************
;
;                                   Display
;
; ****************************************************************************

#include "include.inc"

	.text

; ----------------------------------------------------------------------------
;                        Clear screen (set cursor to 0,0)
; ----------------------------------------------------------------------------
; DESTROYS: R31, R30, R27..R24, R1, R0
; STACK: 6 bytes
; ----------------------------------------------------------------------------

.global DispClear
DispClear:

; ----- switch cursor OFF

	rcall	DispCurOff		; DESTROYS: R31, R30, R24

; ----- prepare board address

	ldi	r30,lo8(Board)
	ldi	r31,hi8(Board)

; ----- initialize first row with cursor

	std	Y+DATA_CURX,R_ZERO	; cursor X
	std	Y+DATA_CURY,R_ZERO	; cursor Y
	std	Y+DATA_CURLINE,r30	; cursor line address
	std	Y+DATA_CURLINE+1,r31
	st	Z+,R_ONE		; length of first row = 1
	std	Y+DATA_CURADDR,r30	; cursor address
	std	Y+DATA_CURADDR+1,r31
	ldi	r24,CH_SPC		; space at cursor position
	st	Z+,r24

; ----- empty all rows

DispClear2:
	st	Z+,R_ZERO
	cpi	r30,lo8(Board+HEIGHT+1)
	brne	DispClear2

; ----- delete unused memory

	ldd	r24,Y+DATA_BOARDEND
	ldd	r25,Y+DATA_BOARDEND+1
	sub	r24,r30
	sbc	r25,r31			; data to delete
; INPUT: R31:R30 (Z) = start address
;	 R25:R24 = number of bytes to delete (>= 0)
;	 R26 = start offset of first pointer to update (OFF_CURLINE,...)
; OUTPUT: R31:R30 (Z) = new end address
;	  R27:R26 (X) = old end address
; DESTROYS: R25, R24, R1, R0
; STACK: 4 bytes
	ldi	r26,OFF_VARADDR		; offset of pointer VarAddr
	rjmp	MemDel

; ----------------------------------------------------------------------------
;                Clear rest of row from cursor position
; ----------------------------------------------------------------------------
; DESTROYS: R31, R30, R27..R24, R1, R0
; STACK: 6 bytes
; ----------------------------------------------------------------------------

.global DispClearRow
DispClearRow:

; ----- switch cursor OFF

	rcall	DispCurOff		; DESTROYS: R31, R30, R24

; ----- validate cursor position

	rcall	DispSetCurOn

; ----- prepare row address

	rcall	DispLine		; row address -> Z, length -> R24

; ----- set new length

	ldd	r25,Y+DATA_CURX		; cursor X
	inc	r25			; including current space character
	st	Z,r25			; set new length of the row

; ----- clear current character

	add	r30,r25			; Z <- current position
	adc	r31,R_ZERO
	sub	r24,r25			; R24 <- length of deleted data
	ldi	r25,CH_SPC
	st	Z,r25			; store space at cursor position

; ----- delete rest of row

	adiw	r30,1			; Z <- end of row
	clr	r25
; INPUT: R31:R30 (Z) = start address
;	 R25:R24 = number of bytes to delete (>= 0)
;	 R26 = start offset of first pointer to update (OFF_CURLINE,...)
; OUTPUT: R31:R30 (Z) = new end address
;	  R27:R26 (X) = old end address
; DESTROYS: R25, R24, R1, R0
; STACK: 4 bytes
	ldi	r26,OFF_VARADDR		; offset of pointer VarAddr
	rjmp	MemDel

; ----------------------------------------------------------------------------
;                      Find address of the row
; ----------------------------------------------------------------------------
; IMPUT: R24 = required row
; OUTPUT: R31:R30 (ZH:ZL) = address of the row (starting with row length)
; DESTROYS: R25, R24
; STACK: 2 bytes
; ----------------------------------------------------------------------------

.global DispFindRow
DispFindRow:

; ----- prepare start of board

	ldi	r30,lo8(Board)
	ldi	r31,hi8(Board)
	rjmp	DispFindRow4

; ----- shift address by length of the row

DispFindRow2:
	ld	r25,Z+
	add	r30,r25
	adc	r31,R_ZERO

; ----- next row

DispFindRow4:
	dec	r24
	brpl	DispFindRow2
	ret

; ----------------------------------------------------------------------------
;                Find address of current row in VRAM
; ----------------------------------------------------------------------------
; OUTPUT: R31:R30 (ZH:ZL) = address of the row (starting with row length)
; DESTROYS: R25, R24
; STACK: 4 bytes
; ----------------------------------------------------------------------------

.global DispRowAddr
DispRowAddr:

	ldd	r24,Y+DATA_CURY
	rcall	DispFindRow
	std	Y+DATA_CURLINE,r30
	std	Y+DATA_CURLINE+1,r31
	ret

; ----------------------------------------------------------------------------
;                    Get cursor address in VRAM (-> Z)
; ----------------------------------------------------------------------------
; OUTPUT: R31:R30 (ZH:ZL) = cursor address in VRAM
; DESTROYS: nothing
; STACK: 2 bytes
; ----------------------------------------------------------------------------

.global DispAddr
DispAddr:
	ldd	r30,Y+DATA_CURADDR
	ldd	r31,Y+DATA_CURADDR+1
	ret

; ----------------------------------------------------------------------------
;                    Get row address and length
; ----------------------------------------------------------------------------
; OUTPUT: R31:R30 (ZH:ZL) = row address in VRAM (= points to length of row)
;	  R24 = row length
; DESTROYS: nothing
; STACK: 2 bytes
; ----------------------------------------------------------------------------

.global DispLine
DispLine:
	ldd	r30,Y+DATA_CURLINE ; Z <- row address
	ldd	r31,Y+DATA_CURLINE+1
	ld	r24,Z		; R24 <- row length
	ret

; ----------------------------------------------------------------------------
;                Trim trailing spaces on end of row
; ----------------------------------------------------------------------------
; INPUT: R24 = row
; DESTROYS: R31, R30, R27..R24, R1, R0
; STACK: 4 bytes
; ----------------------------------------------------------------------------

.global DispTrimRow
DispTrimRow:

; ----- prepare minimal length -> R0

	clr	r0		; row without cursor can have length 0	
	ldd	r25,Y+DATA_CURY	; R25 <- current row
	cp	r24,r25		; row with cursor?
	brne	DispTrimRow2	; no cursor
	ldd	r0,Y+DATA_CURX	; cursor X position
	inc	r0		; minimal length

; ----- find row -> Z

DispTrimRow2:
	rcall	DispFindRow	; DESTROYS: R25, R24
	clr	r1		; R1 <- 0 counter of deleted characters
	ld	r24,Z		; R24 <- length

; ----- save start of row -> X

	movw	r26,r30

; ----- pointer to last character -> Z

	add	r30,r24
	adc	r31,R_ZERO

; ----- check row length

DispTrimRow4:
	cp	r0,r24		; check length
	brcc	DispTrimRow6	; row is already too short
	tst	r24		; zero length?
	breq	DispTrimRow6	; row has zero length

; ----- check last character

	ld	r25,Z		; get last character
	cpi	r25,CH_SPC	; space?
	brne	DispTrimRow6	; not space

; ----- delete this character

	dec	r24		; decrease row length
	inc	r1		; increase delete counter
	sbiw	r30,1		; shift Z pointer
	rjmp	DispTrimRow4	; next character

; ----- is there something to delete?

DispTrimRow6:
	tst	r1		; check number of characters
	breq	DispTrimRow8	; nothing to delete

; ----- save new row length

	movw	r30,r26		; Z <- row address
	st	Z+,r24		; save new row length

; ----- start of deleted data

	add	r30,r24		; Z <- new end of row
	adc	r31,R_ZERO

; ----- check cursor address

	ldd	r24,Y+DATA_CURLINE ; cursor line
	ldd	r25,Y+DATA_CURLINE+1
	cp	r26,r24		; check row address
	cpc	r27,r25
	ldi	r26,OFF_CURLINE	; start offset = cursor
	brcs	DispTrimRow7	; cursor correction
	ldi	r26,OFF_VARADDR	; no correction

; ----- delete characters

DispTrimRow7:
	mov	r24,r1		; R24 <- number of bytes to delete
	clr	r25
; INPUT: R31:R30 (Z) = start address
;	 R25:R24 = number of bytes to delete (>= 0)
;	 R26 = start offset of first pointer to update (OFF_CURLINE,...)
; OUTPUT: R31:R30 (Z) = new end address
;	  R27:R26 (X) = old end address
; DESTROYS: R25, R24, R1, R0
; STACK: 4 bytes
	rjmp	MemDel	

DispTrimRow8:
	ret

; ----------------------------------------------------------------------------
;                Trim trailing spaces from all rows
; ----------------------------------------------------------------------------
; DESTROYS: R31, R30, R27..R23, R1, R0
; STACK: 6 bytes
; ----------------------------------------------------------------------------

.global DispTrimAll
DispTrimAll:
	clr	r23
DispTrimAll2:
	mov	r24,r23
	rcall	DispTrimRow
	inc	r23
	cpi	r23,HEIGHT
	brne	DispTrimAll2
	ret

; ----------------------------------------------------------------------------
;                       Set cursor visible ON
; ----------------------------------------------------------------------------
; DESTROYS: R31, R30, R24
; STACK: 4 bytes
; ----------------------------------------------------------------------------

.global DispCurOn
DispCurOn:
	ldd	r24,Y+DATA_CURVIS ; cursor visible flag
	tst	r24		; is cursor visible?
	brne	DispCurFlip8	; already visible

; DispCurFlip must follow

; ----------------------------------------------------------------------------
;                       Flip cursor visibility
; ----------------------------------------------------------------------------
; DESTROYS: R31, R30, R24
; STACK: 4 bytes
; ----------------------------------------------------------------------------

.global DispCurFlip
DispCurFlip:
	rcall	DispAddr	; get cursor address -> Z
	ld	r24,Z		; get character
	subi	r24,0x80	; flip inversion flag
	st	Z,r24		; set character

	ldd	r24,Y+DATA_CURVIS ; cursor visibility flag
	com	r24		; invert
	std	Y+DATA_CURVIS,r24 ; save new flag
DispCurFlip8:
	ret

; ----------------------------------------------------------------------------
;                    Blinking cursor service
; ----------------------------------------------------------------------------
; DESTROYS: R31, R30, R24
; STACK: 4 bytes
; ----------------------------------------------------------------------------

.global DispCurBlink
DispCurBlink:
	ldd	r24,Y+DATA_FRAME ; current frame
	andi	r24,0x10	; check time
	brne	DispCurOn	; cursor ON

; DispCurOff must follow

; ----------------------------------------------------------------------------
;                       Set cursor visible OFF
; ----------------------------------------------------------------------------
; DESTROYS: R31, R30, R24
; STACK: 4 bytes
; ----------------------------------------------------------------------------

.global DispCurOff
DispCurOff:
	ldd	r24,Y+DATA_CURVIS ; cursor visible flag
	tst	r24		; is cursor visible?
	brne	DispCurFlip	; flip cursor
	ret

; ----------------------------------------------------------------------------
;                     Scroll screen UP (updates cursor)
; ----------------------------------------------------------------------------
; DESTROYS: R31, R30, R27..R24, R1, R0
; STACK: 6 bytes
; ----------------------------------------------------------------------------

.global DispScroll
DispScroll:

; ----- set cursor OFF

	rcall	DispCurOff	; set cursor OFF (DESTROYS: R31, R30, R24)

; ----- start of first row -> Z

	ldi	r30,lo8(Board)
	ldi	r31,hi8(Board)

; ----- size of first row (including length byte) -> R24

	ld	r24,Z
	inc	r24

; ----- start of second row -> X

	movw	r26,r30
	add	r26,r24
	adc	r27,R_ZERO

; ----- check if cursor is already at first row

	ldd	r25,Y+DATA_CURY
	tst	r25
	brne	DispScroll4	; not on firt row

; ----- scroll will start from second row

	movw	r30,r26

; ----- size of second row (including length byte) -> R24

	ld	r24,Z
	inc	r24

; ----- start of third row -> X

	add	r26,r24
	adc	r27,R_ZERO
	rjmp	DispScroll8

; ----- repair cursor address

DispScroll4:
	dec	r25
	std	Y+DATA_CURY,r25	; new cursor row

	ldd	r25,Y+DATA_CURLINE ; cursor line address LOW
	sub	r25,r24
	std	Y+DATA_CURLINE,r25
	ldd	r25,Y+DATA_CURLINE+1 ; cursor line address HIGH
	sbc	r25,R_ZERO
	std	Y+DATA_CURLINE+1,r25

	ldd	r25,Y+DATA_CURADDR ; cursor address LOW
	sub	r25,r24
	std	Y+DATA_CURADDR,r25
	ldd	r25,Y+DATA_CURADDR+1 ; cursor address HIGH
	sbc	r25,R_ZERO
	std	Y+DATA_CURADDR+1,r25

; ----- size of data -> R25:R24

DispScroll8:
	ldd	r24,Y+DATA_BOARDEND
	ldd	r25,Y+DATA_BOARDEND+1
	sub	r24,r26
	sbc	r25,r27

; ----- move data

; INPUT: R31:R30 (Z) = destination address
;	 R27:R26 (X) = source address
;	 R25:R24 = length (>= 0)
; OUTPUT: R31:R30 (Z) = destination address + length
;	  R27:R26 (X) = source address + length
; DESTROYS: R25, R24, R0
; STACK: 2 bytes
	rcall	MemCpy

; ----- initialize last empty row

	st	Z+,R_ZERO

; ----- delete rest of data

	sub	r26,r30
	sbc	r27,r31
	movw	r24,r26		; size of deleted data
; INPUT: R31:R30 (Z) = start address
;	 R25:R24 = number of bytes to delete (>= 0)
;	 R26 = start offset of first pointer to update (OFF_CURLINE,...)
; OUTPUT: R31:R30 (Z) = new end address
;	  R27:R26 (X) = old end address
; DESTROYS: R25, R24, R1, R0
; STACK: 4 bytes
	ldi	r26,OFF_VARADDR		; offset of pointer VarAddr
	rjmp	MemDel

; ----------------------------------------------------------------------------
;               Display reduction to ensure enough free memory
; ----------------------------------------------------------------------------
; DESTROYS: R31, R30, R27..R18, R1, R0
; STACK: 8 bytes
; ----------------------------------------------------------------------------

; ----- scroll display

DispReduce2:
	rcall	DispScroll

.global DispReduce
DispReduce:

; ----- get free memory -> R25:R24

	ldi	r24,34		; required reserve (for edit line)
	rcall	MemCheck	; check free memory (stack 6 bytes)
	brpl	DispReduce9	; free space is OK

; ----- get display size -> R25:R24

	ldd	r24,Y+DATA_BOARDEND
	ldd	r25,Y+DATA_BOARDEND+1
	subi	r24,lo8(Board)
	sbci	r25,hi8(Board)

; ----- check minimal display size

	cpi	r24,HEIGHT + WIDTH + 4
	cpc	r25,R_ZERO
	brcc	DispReduce2	; size is OK, scroll display

DispReduce9:
	ret

; ----------------------------------------------------------------------------
;                          Display unsigned number
; ----------------------------------------------------------------------------
; INPUT: R19:R18 = number to display (5 digits)
;	 with DispNum???: R22 = substitute character instead of trailing zeroes (CH_NUL=nothing)
; DESTROYS: R27..R18, R1, R0
; STACK: 16 bytes
; ----------------------------------------------------------------------------

; ----- DispNum - no trailing zeroes

.global DispNum
DispNum:
	ldi	r22,CH_NUL

; ----- increment is 10000

.global DispNum10000
DispNum10000:
	ldi	r20,lo8(10000)
	ldi	r21,hi8(10000)
	rcall	DispNumDig

; ----- increment is 1000

.global DispNum1000
DispNum1000:
	ldi	r20,lo8(1000)
	ldi	r21,hi8(1000)
	rcall	DispNumDig

; ----- increment is 100

.global DispNum100
DispNum100:
	ldi	r20,100
	rcall	DispNumDig2

; ----- increment is 10

.global DispNum10
DispNum10:
	ldi	r20,10
	rcall	DispNumDig2

; ----- increment is 1

.global DispNum1
DispNum1:
	ldi	r20,1
	ldi	r22,CH_0	; use zero all time

; ----- prepare next digit

DispNumDig2:
	clr	r21		; increment HIGH = 0

DispNumDig:
	ldi	r24,CH_0	; start with character '0'
1:	inc	r24		; increase character
	sub	r18,r20		; decrease number
	sbc	r19,r21
	brcc	1b		; no overflow
	dec	r24		; return character
	add	r18,r20		; return last positive number
	adc	r19,r21
	
; ----- print zero

	cpi	r24,CH_0
	brne	DispNumDig4
	mov	r24,r22		; use substitute character
	rjmp	DispCh

; ----- display character (R22 = substitute character)

DispNumDig4:
	ldi	r22,CH_0	; stop trailing
	rjmp	DispCh

; ----------------------------------------------------------------------------
;                  Display HEX word to cursor position
; ----------------------------------------------------------------------------
; INPUT: R25:R24 = word to display
; DESTROYS: R27..R23, R1, R0
; STACK: 18 bytes
; ----------------------------------------------------------------------------

.global DispHexW
DispHexW:
	push	r24
	mov	r24,r25
	rcall	DispHexB
	pop	r24

; DispHexB must follow

; ----------------------------------------------------------------------------
;                  Display HEX byte to cursor position
; ----------------------------------------------------------------------------
; INPUT: R24 = byte to display
; DESTROYS: R27..R23, R1, R0
; STACK: 15 bytes
; ----------------------------------------------------------------------------

.global DispHexB
DispHexB:
	push	r24
	swap	r24
	rcall	DispHexN
	pop	r24

; DispHexN must follow

; ----------------------------------------------------------------------------
;                  Display HEX nibble to cursor position
; ----------------------------------------------------------------------------
; INPUT: R24 = nibble to display
; DESTROYS: R27..R23, R1, R0
; STACK: 12 bytes
; ----------------------------------------------------------------------------

.global DispHexN
DispHexN:
	andi	r24,0x0f
	subi	r24,-CH_0
	cpi	r24,CH_9+1
	brcs	DispCh
	subi	r24,-7
	rjmp	DispCh
	
; ----------------------------------------------------------------------------
;              Display space character to cursor position
; ----------------------------------------------------------------------------
; DESTROYS: R27..R23, R1, R0
; STACK: 12 bytes
; ----------------------------------------------------------------------------

.global DispSpc
DispSpc:
	ldi	r24,CH_SPC

; DispCh must follow

; ----------------------------------------------------------------------------
;               Output character to cursor position (with save Z)
; ----------------------------------------------------------------------------
; INPUT: R24 = character to display
; DESTROYS: R27..R23, R1, R0
; STACK: 12 bytes
; ----------------------------------------------------------------------------

.global DispCh
DispCh:
	push	r30
	push	r31

	rcall	DispCh0

	pop	r31
	pop	r10
	ret

; ----------------------------------------------------------------------------
;                  Output character to cursor position
; ----------------------------------------------------------------------------
; INPUT: R24 = character to display
; DESTROYS: R31, R30, R27..R23, R1, R0
; STACK: 10 bytes
; ----------------------------------------------------------------------------

.global DispCh0
DispCh0:

; ----- no character (required by DispNum)

	cpi	r24,CH_NUL
	breq	DispChRet

; ----- set cursor OFF

	mov	r25,r24		; R25 <- character
	rcall	DispCurOff	; set cursor OFF (DESTROYS: R31, R30, R24)

; ----- cursor left

	cpi	r25,CH_LEFT
	brne	DispCh2
	ldd	r23,Y+DATA_CURX	; current cursor X
	cpi	r23,0
	breq	DispChRet	; already first position
	dec	r23		; decrease position
	rjmp	DispSetCurX	; set cursor	

; ----- cursor up

DispCh2:
	cpi	r25,CH_UP
	brne	DispCh3
	ldd	r25,Y+DATA_CURY	; cursor Y
	cpi	r25,0
	breq	DispChRet	; already first row

	dec	r25		; shift cursor Y
	ldd	r24,Y+DATA_CURX ; current X position
	rcall	DispSetCur	; set cursor

	ldd	r24,Y+DATA_CURY
	inc	r24
	rjmp	DispTrimRow	; trim previous row

; ----- cursor right

DispCh3:
	cpi	r25,CH_RIGHT
	brne	DispCh32
	rjmp	DispChRight	; increase cursor X

; ----- cursor down

DispCh32:
	cpi	r25,CH_DOWN
	brne	DispCh4
	ldd	r23,Y+DATA_CURX ; current X position
	rjmp	DispDownLine	; shift line down

; ----- Home (start of row)

DispCh4:
	cpi	r25,CH_HOME
	brne	DispCh42
	ldi	r23,0		; cursor X at start of row
	rjmp	DispSetCurX	; set cursor	

; ----- Tab

DispCh42:
	cpi	r25,CH_TB
	brne	DispCh44
	ldd	r23,Y+DATA_CURX ; current X position
	subi	r23,-4
	andi	r23,0xfc	; round to 4-position
	cpi	r23,WIDTH-1
	brcs	DispCh43
	ldi	r23,WIDTH-1
DispCh43:
	rjmp	DispSetCurX

DispChRet:
	ret

; ----- BackTab

DispCh44:
	cpi	r25,CH_BACKTB
	brne	DispCh5
	ldd	r23,Y+DATA_CURX ; current X position
	tst	r23
	breq	DispChRet
	dec	r23
	andi	r23,0xfc	; round to 4-position
	rjmp	DispSetCurX

; ----- End (end of row)

DispCh5:
	cpi	r25,CH_END
	brne	DispCh56

	; check if row is empty
	rcall	DispLine	; get row address (Z) and length (R24)
	tst	r24		; is row empty?
	breq	DispChRet	; row is empty

	; row is full - set to last character
	mov	r23,r24
	cpi	r23,WIDTH	; is row full?
	brcc	DispCh52	; row is full

	; get last character on the row
	add	r30,r23		; address of last character
	adc	r31,R_ZERO
	ld	r25,Z		; R25 <- last character
	cpi	r25,CH_SPC	; space?
	brne	DispCh54	; not space, skip behind it

	; shift cursor to end of row
DispCh52:
	dec	r23		; position of last character
DispCh54:
	rjmp	DispSetCurX	; set cursor	

; ----- PageUp

DispCh56:
	cpi	r25,CH_PGUP
	brne	DispCh58
	clr	r24
	clr	r25
	rjmp	DispSetCur

; ----- PageDown

DispCh58:
	cpi	r25,CH_PGDN
	brne	DispCh6
	clr	r24
	ldi	r25,HEIGHT-1
	rjmp	DispSetCur

; ----- BackSpace (Rubout)

DispCh6:
	cpi	r25,CH_BS
	brne	DispCh62

	ldd	r23,Y+DATA_CURX	; current cursor X
	cpi	r23,0
	breq	DispChRet	; already first position
	dec	r23		; decrease position
	rcall	DispSetCurX	; set cursor	
	rjmp	DispCh72	; delete character at cursor position

; ----- clear screen

DispCh62:
	cpi	r25,CH_FF
	brne	DispCh64
	rjmp	DispClear	; clear screen

; ----- Shift+BackSpace (clear rest of row)

DispCh64:
	cpi	r25,CH_DELROW
	brne	DispCh7
	rjmp	DispClearRow	; clear rest of row

; ----- Delete

DispCh7:
	cpi	r25,CH_DELETE
	brne	DispCh8
DispCh72:
	rcall	DispLine	; get row address (Z) and length (R24)
	ldd	r25,Y+DATA_CURX	; X position
	cp	r25,r24		; check position
	brcc	DispChRet	; invalid position
	adc	r30,r25		; Z <- cursor address (Z+length+1)
	adc	r31,R_ZERO
	clr	r25
	ldi	r24,1		; 1 character to delete

; INPUT: R31:R30 (Z) = start address
;	 R25:R24 = number of bytes to delete (>= 0)
;	 R26 = start offset of first pointer to update (OFF_CURLINE,...)
; OUTPUT: R31:R30 (Z) = new end address
;	  R27:R26 (X) = old end address
; DESTROYS: R25, R24, R1, R0
; STACK: 4 bytes
	ldi	r26,OFF_VARADDR		; offset of pointer VarAddr
	rcall	MemDel		; delete 1 character

	rcall	DispLine	; get row address (Z) and length (R24)
	dec	r24		; decrease row length
	st	Z,r24		; set new length

; ----- reload current cursor position

DispSetCurOn:

	ldd	r23,Y+DATA_CURX	; X position
	rjmp	DispSetCurX	; set cursor

; ----- Insert

DispCh8:
	cpi	r25,CH_INSERT
	brne	DispCh86

	rcall	DispLine	; get row address (Z) and length (R24)
	ldd	r25,Y+DATA_CURX	; R25 <- cursor position
	cpi	r24,WIDTH	; is row already full?
	brcs	DispCh82	; row is not full

	; insert space if row is full
	add	r30,r24		; Z <- address of last character
	adc	r31,R_ZERO
	movw	r26,r30		; X <- address of last character
	adiw	r30,1		; Z <- address of end of row
	sub	r24,r25		; length of rest of row
	dec	r24		; R24 = size of moved data
	clr	r25
; INPUT: R31:R30 (Z) = destination address + length
;	 R27:R26 (X) = source address + length
;	 R25:R24 = length (>= 0)
; OUTPUT: R31:R30 (Z) = destination address
;	  R27:R26 (X) = source address
; DESTROYS: R25, R24, R0
; STACK: 2 bytes
	rcall	MemCpyD		; move rest of row
	rjmp	DispCh84

DispCh82:
	; insert space if row is not full
	inc	r24		; increase row length
	st	Z,r24		; save new length
	adiw	r30,1		; Z <- start of row
	add	r30,r25		; Z <- address of cursor
	adc	r31,R_ZERO
	ldi	r24,1		; insert 1 character
	clr	r25
; INPUT: R31:R30 (Z) = start address
;	 R25:R24 = number of bytes to insert (>= 0)
; OUTPUT: R31:R30 (Z) = start address + number of bytes
;	  R27:R26 (X) = start address
; DESTROYS: R25, R24, R1, R0
; STACK: 4 bytes
	ldi	r26,OFF_VARADDR		; offset of pointer VarAddr
	rcall	MemIns			; insert data (stack 6 bytes)
DispCh84:
	ldi	r24,CH_SPC
	st	X,r24		; write space
	ret

; ----- Shift+Enter (insert row)

DispCh86:
	cpi	r25,CH_LINE
	brne	DispCh88

	; scroll if it is last row
	ldd	r25,Y+DATA_CURY	; cursor Y
	cpi	r25,HEIGHT-1	
	brne	DispCh87
	rcall	DispScroll	; DESTROYS: R31, R30, R27..R24, R1, R0, STACK: 6 bytes

	; delete last row
DispCh87:
	ldi	r24,HEIGHT-1
	rcall	DispFindRow
	ld	r24,Z
	inc	r24
	clr	r25
	ldi	r26,OFF_VARADDR		; offset of pointer VarAddr
	rcall	MemDel		; delete last row

	; create new row
	ldd	r24,Y+DATA_CURY ; cursor Y
	inc	r24
	rcall	DispFindRow
	ldi	r24,1
	clr	r25
	ldi	r26,OFF_VARADDR		; offset of pointer VarAddr
	rcall	MemIns		; insert new ror (stack 6 bytes)
	st	X,R_ZERO

	; set new cursor
	ldd	r25,Y+DATA_CURY
	inc	r25
	clr	r24
	rcall	DispSetCur

	; trim previous row
	ldd	r24,Y+DATA_CURY
	dec	r24
	rjmp	DispTrimRow

; ----- scroll

DispCh88:
	cpi	r25,CH_SCROLLUP
	brne	DispCh9
	rjmp	DispScroll

; ----- new line

DispCh9:
	cpi	r25,CH_ENTER
	breq	DispNewLine

; ----- output character

	rcall	DispAddr	; get address in VRAM
	st	Z,r25		; output character

; ----- increase cursor X

DispChRight:
	ldd	r23,Y+DATA_CURX	; cursor X
	inc	r23	
	cpi	r23,WIDTH	; check max. position
	brcs	DispSetCurX	; set new cursor position
	ret			; cursor not changed

; ----------------------------------------------------------------------------
;                         Skip cursor to new line
; ----------------------------------------------------------------------------
; INPUT: R23 = cursor X (only in case of DispDownLine, or R23=0 if DispNewLine)
; OUTPUT: R31:R30 (Z) = cursor address
; DESTROYS: R27..R23, R1, R0
; STACK: 8 bytes
; ----------------------------------------------------------------------------

.global DispNewLine
DispNewLine:
	clr	r23		; cursor X = 0

; ----- line down (with column R23)
	
.global DispDownLine
DispDownLine:

; ----- set cursor OFF

	rcall	DispCurOff	; set cursor OFF (DESTROYS: R31, R30, R24)

; ----- set cursor to next row

	ldd	r25,Y+DATA_CURY	; cursor Y
	inc	r25		; increase cursor
	cpi	r25,HEIGHT	; check max. row
	brcs	DispNewLine2	; cursor is OK

; ----- scroll display up

	rcall	DispScroll	; DESTROYS: R31, R30, R27..R24, R1, R0, STACK: 6 bytes
	ldi	r25,HEIGHT-1

; ------ move cursor and trim previous row

DispNewLine2:
	rcall	DispNewLine4	; move cursor
	ldd	r24,Y+DATA_CURY
	dec	r24		; previous row
	rjmp	DispTrimAll	; trim previous row

; ----- set cursor R23=X, use current Y

DispSetCurX:
	ldd	r25,Y+DATA_CURY	; cursor Y

; ----- set cursor to next row

DispNewLine4:
	mov	r24,r23		; cursor X

; DispSetCur must follow

; ----------------------------------------------------------------------------
;                        Set cursor position and address
; ----------------------------------------------------------------------------
; INPUT: R24 = column (0...)
;	 R25 = row (0...)
; OUTPUT: R31:R30 (Z) = cursor address
; DESTROYS: R27..R24, R1, R0
; STACK: 8 bytes
; ----------------------------------------------------------------------------

.global DispSetCur
DispSetCur:

; ----- save cursor column (-> R0) and row (-> R1)

	movw	r0,r24

; ----- switch cursor OFF

	rcall	DispCurOff		; DESTROYS: R31, R30, R24

; ----- check if cursor row has been changed

	ldd	r25,Y+DATA_CURY		; current row
	cp	r25,r1			; check new row
	breq	DispSetCur2		; cursor row not changed

; ----- find cursor address -> Z

	std	Y+DATA_CURY,r1		; set new cursor row
	rcall	DispRowAddr		; find cursor address -> Z (DESTROYS: R25, R24)

; ----- load row address (-> Z) and length (-> R24)

DispSetCur2:
	rcall	DispLine	; get row address (Z) and length (R24)

; ----- save new cursor column

	std	Y+DATA_CURX,r0

; ----- prepare cursor address -> Z

	adiw	r30,1
	add	r30,r0
	adc	r31,R_ZERO

; ----- check if row is big enough

	sub	r0,r24			; check cursor column
	brcs	DispSetCur6		; cursor is OK

; ----- insert new space

	sub	r30,r0
	sbc	r31,R_ZERO		; Z <- address of end of row
	mov	r24,r0			; number of bytes LOW-1
	inc	r24			; number of bytes LOW
	clr	r25			; number of bytes HIGH = 0

; INPUT: R31:R30 (Z) = start address
;	 R25:R24 = number of bytes to insert (>= 0)
; OUTPUT: R31:R30 (Z) = start address + number of bytes
;	  R27:R26 (X) = start address
; DESTROYS: R25, R24, R1, R0
; STACK: 4 bytes
	ldi	r26,OFF_VARADDR		; offset of pointer VarAddr
	rcall	MemIns			; insert data (stack 6 bytes)

; ----- write spaces to new positions

	ldi	r24,CH_SPC
DispSetCur4:
	st	X+,r24
	cp	r26,r30
	brne	DispSetCur4

; ----- return last character

	sbiw	r30,1			; return address of last character

; ----- set new length of the row

	ldd	r26,Y+DATA_CURLINE	; X <- address of current row
	ldd	r27,Y+DATA_CURLINE+1
	mov	r24,r30			; cursor address LOW
	sub	r24,r26			; row length
	st	X,r24			; set new row length

; ----- save cursor address	

DispSetCur6:
	std	Y+DATA_CURADDR,r30
	std	Y+DATA_CURADDR+1,r31
	ret

; ----------------------------------------------------------------------------
;             Display text from ROM, terminated with bit 7 set
; ----------------------------------------------------------------------------
; INPUT: R31:R30 (Z) = text in ROM
; OUTPUT: R24 = last character (with bit 7 set)
;	  R31:R30 (Z) = pointer behind end of text
; DESTROYS: R27..R23, R21, R20, R1, R0
; STACK: 14 bytes
; ----------------------------------------------------------------------------

.global DispText
DispText:
	lpm	r24,Z			; character to display
DispText2:
	andi	r24,0x7f		; reset bit 7

; INPUT: R24 = character to display
; DESTROYS: R27..R23, R1, R0
; STACK: 12 bytes
	rcall	DispCh

	lpm	r24,Z+			; load last character again
	tst	r24			; test bit 7
	brpl	DispText2		; display next character
	ret
