I really like it when things start to achieve critical mass - when there are enough bits of framework already done that adding something new becomes less of a deep-thinking exercise and more a question of connecting things together in the right order. That's what happened with the cursor task, because it took no more than about an hour to write the code necessary to provide a typing-point cue on the screen:
- Define and track the cursor position
- Draw a cursor at the appropriate screen position
- Manage settings for solid-block or underline visual modes
- Manage settings for blinking or steady display mode (with variable timing on the blink)
- Manage settings for disabling the cursor (i.e. for later when running user code)
I already had the settings-bytes defined in Zero Page, so all I actually had to write was some code to map the cursor X/Y position to the bitmap and a bit of logic to draw/undraw the cursor itself. There are a couple of support routines which manage forced-undrawing if necessary (when the cursor changes position, or changes visual mode, or is disabled - we don't want cursor-corpses left all over the screen) but in total I spent maybe 45 minutes writing code which is pretty small, nice and fast, and took around 15 minutes to debug; I forgot that cursor positioning is one-based (to make it easy for the user to use in screen-referencing) but the bitmap is of course zero-based, and there were a few nonplussed minutes whilst I suffered cognitive failure when the cursor wasn't where I expected it to be.
The control logic is written as a series of subroutines, each of which falls into the next - this means that various bits of control work can be called individually and have the subsequent effects happen automatically. We start with updtcrsr, which is called from the ISR and manages the countdown timer for cursor blinking; if there's no blink due, it just exits, otherwise it falls through into setcsrst which resets the timer and checks to see if the cursor is in blink or steady mode. In blink mode we drop into setcsrvb which flips the 'is-visible' bit before finally entering drawcrsr, which figures-out whether the cursor is in block or underline mode, which column of the two-glyph byte it's in, and then does a straightforward Exclusive-OR of that glyph to draw or undraw the cursor:
We will want to disable the cursor when running user code later - this routine switches the cursor off, and calls the drawing logic to get rid of the cursor if it was on when we disabled it:
And here's the code to map the cursor X/Y coordinates to a bitmap position. You might notice that it's lifted from drawdrty, and that's because it's doing much the same thing - except that instead of mapping a dirty row to the bitmap, we're determining where the cursor should be drawn. There are some notable differences, of course - we first update the coordinates with whatever was passed in the .X and .Y registers and make sure we undraw the cursor at its' old position. Then the .update logic probably looks very familiar, as it's practically the same as in drawdrty except that we convert one-based cursor values to zero-based equivalents for the bitmap lookup, and add six at the end if we're drawing the cursor as an underline rather than a block:
I considered optimising and combining this with the equivalent bit of logic in drawdrty because the code is 90% identical, but decided against it as I'd have to add a bunch of conditional checks to figure-out whether it was calculating a dirty-row address or a cursor address - and since drawdrty is designed to run as quickly as possible, I didn't like the idea of slowing it down just to save a couple of dozen bytes. The cursor control logic is called from the ISR though, with a simple bit-test ahead of it to determine whether it's active or not - later on, when we get to the stage where the user is running code, we have a mechanism to disable the display of the cursor during screen updates so that we don't slow things down by frantically trying to draw it as stuff is happening.
The control logic is written as a series of subroutines, each of which falls into the next - this means that various bits of control work can be called individually and have the subsequent effects happen automatically. We start with updtcrsr, which is called from the ISR and manages the countdown timer for cursor blinking; if there's no blink due, it just exits, otherwise it falls through into setcsrst which resets the timer and checks to see if the cursor is in blink or steady mode. In blink mode we drop into setcsrvb which flips the 'is-visible' bit before finally entering drawcrsr, which figures-out whether the cursor is in block or underline mode, which column of the two-glyph byte it's in, and then does a straightforward Exclusive-OR of that glyph to draw or undraw the cursor:
updtcrsr SUBROUTINE
DEC _CURSORT ; [5] ZP decrement cursor timer
BNE _crsrdone ; [3/2] no update until timer hits zero
LDA _CURSORM ; [3] ZP get cursor mode
setcsrst SUBROUTINE
TAX ; [2] stash in .X
AND #111111 ; [2] mask timer reset value
STA _CURSORT ; [3] ZP set cursor timer
TXA ; [2] get cursor mode back from .X
ASL ; [2] shift b7 to Carry
BCC _crsrdone ; [2/3] skip blink logic
LDA _CURSORS ; [3] ZP get cursor state
setcsrvb SUBROUTINE
EOR #$80 ; [2] invert visibility bit
STA _CURSORS ; [3] ZP set cursor state
drawcrsr SUBROUTINE
LDY #$07 ; [2] bitmap byte index for block mode
TXA ; [2] get cursor mode back from .X
AND #%01000000 ; [2] mask cursor style
BNE .invert ; [3/2] skip if cursor is block style
LDY #$01 ; [2] bitmap byte index for underline mode
.invert
LDA _CURSORC ; [4] get cursor column
AND #$01 ; [2] mask LSB
TAX ; [2] move to .X for EOR mask index
.nextbyte
LDA (_CURSORA),Y ; [5] get bitmap byte under cursor
EOR _CHARDATA,X ; [4] apply inverse overlay
STA (_CURSORA),Y ; [6] save back to bitmap
DEY ; [2] decrement index
BPL .nextbyte ; [3/2] loop for next byte
_crsrdone
RTS ; [6]
We will want to disable the cursor when running user code later - this routine switches the cursor off, and calls the drawing logic to get rid of the cursor if it was on when we disabled it:
toglcrsr SUBROUTINE
LDA _CURSORS ; [3] ZP get cursor state
EOR #$60 ; [2] invert active bit
BEQ .done ; [2/3] if cursor is not visible we're done
BNE setcsrvb ; [3/3] always branch, undraw cursor
.done
STA _CURSORS ; [3] ZP set cursor state
RTS ; [6]
And here's the code to map the cursor X/Y coordinates to a bitmap position. You might notice that it's lifted from drawdrty, and that's because it's doing much the same thing - except that instead of mapping a dirty row to the bitmap, we're determining where the cursor should be drawn. There are some notable differences, of course - we first update the coordinates with whatever was passed in the .X and .Y registers and make sure we undraw the cursor at its' old position. Then the .update logic probably looks very familiar, as it's practically the same as in drawdrty except that we convert one-based cursor values to zero-based equivalents for the bitmap lookup, and add six at the end if we're drawing the cursor as an underline rather than a block:
setcsrrc SUBROUTINE
STX _CURSORC ; [4] set cursor column
STY _CURSORR ; [4] set cursor row
LDA _CURSORS ; [3] ZP get cursor state
ASL ; [2] shift visibility bit to Carry
BCC .update ; [2/3] skip to update if not visible
LDX _CURSORM ; [3] ZP get cursor mode
JSR drawcrsr ; [6] undraw cursor
.update
LDX _CURSORC ; [4] get cursor column
DEX ; [2] decrement for zero-based table
TXA ; [2] move column to .A
AND #%11111110 ; [2] make value even
TAY ; [2] move to .Y
LDX _CURSORR ; [4] get cursor row
DEX ; [2] decrement for zero-based table
CMP #$18 ; [2] is this line 25?
BEQ .line25 ; [2/3] yep, skip stuff for lines 1-24
TXA ; [2] move row to .A
ASL ; [2] multiply by 8
ASL ; [2]
ASL ; [2]
ADC _COLADDRS,Y ; [4] add bitmap column address lo-byte
STA _CURSORA ; [3] ZP set cursor draw address lo-byte
LDA _COLADDRS+1,Y ; [4] get bitmap column address hi-byte
ADC #$00 ; [2] add Carry
BNE .sethi ; [3/3] can never be zero, always branch
.line25
TYA ; [2] move column for multiply
ASL ; [2] multiply by 4 for line 25 offset
ASL ; [2]
STA _CURSORA ; [3] ZP set cursor draw address lo-byte
LDA #$00 ; [2]
.sethi
STA _CURSORA+1 ; [3] ZP set cursor draw address hi-byte
LDA _CURSORM ; [3] ZP get cursor mode
AND #%01000000 ; [2] mask cursor style
BNE drawcrsr ; [3/2] cursor is using block style, no increment
LDA _CURSORA ; [3] ZP get cursor draw address lo-byte
ADC #$06 ; [2] add 6 for underline mode
STA _CURSORA ; [3] ZP set cursor draw address lo-byte
.draw
BNE drawcrsr ; [3/3] draw cursor
I considered optimising and combining this with the equivalent bit of logic in drawdrty because the code is 90% identical, but decided against it as I'd have to add a bunch of conditional checks to figure-out whether it was calculating a dirty-row address or a cursor address - and since drawdrty is designed to run as quickly as possible, I didn't like the idea of slowing it down just to save a couple of dozen bytes. The cursor control logic is called from the ISR though, with a simple bit-test ahead of it to determine whether it's active or not - later on, when we get to the stage where the user is running code, we have a mechanism to disable the display of the cursor during screen updates so that we don't slow things down by frantically trying to draw it as stuff is happening.
To finalise cursor positioning testing, I need to be able to move it around the screen. I could just write a little test harness to tweak the cursor coordinates and call the reset-position routine, but I'd quite like to get a little interactivity into the system at this point - even if only in a small way. And that means delving into keyboard scanning, which is a new area of exploration and one which is going to bring us back to our old friend VIA #2, as it is this chip which handles the keyboard interface.
But before I do that, I thought I'd have a little play-around with some numeric display logic so that I can show you a screenshot next time with something interesting on it... ;)
But before I do that, I thought I'd have a little play-around with some numeric display logic so that I can show you a screenshot next time with something interesting on it... ;)
No comments:
Post a Comment