WeeGUI/mouse.s

674 lines
15 KiB
ArmAsm

;
; mouse.s
; Routines for handling the mouse
;
; Created by Quinn Dunki on 8/15/14.
; Copyright (c) 2014 One Girl, One Laptop Productions. All rights reserved.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ProDOS ROM entry points and constants
;
PRODOS_MLI = $bf00
ALLOC_INTERRUPT = $40
DEALLOC_INTERRUPT = $41
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Mouse firmware ROM entry points and constants
;
; These mouse firmware entry points are offsets from the firmware
; entry point of the slot, and also indirect.
SETMOUSE = $12
SERVEMOUSE = $13
READMOUSE = $14
CLEARMOUSE = $15
POSMOUSE = $16
CLAMPMOUSE = $17
HOMEMOUSE = $18
INITMOUSE = $19
MOUSTAT = $0778 ; + Slot Num
MOUSE_XL = $0478 ; + Slot Num
MOUSE_XH = $0578 ; + Slot Num
MOUSE_YL = $04f8 ; + Slot Num
MOUSE_YH = $05f8 ; + Slot Num
MOUSE_CLAMPL = $04f8 ; Upper mouse clamp (LSB). Slot independent.
MOUSE_CLAMPH = $05f8 ; Upper mouse clamp (MSB). Slot independent.
MOUSE_ZEROL = $0478 ; Zero value of mouse (LSB). Slot independent.
MOUSE_ZEROH = $0578 ; Zero value of mouse (MSB). Slot independent.
MOUSTAT_MASK_BUTTONINT = %00000100
MOUSTAT_MASK_MOVEINT = %00000010
MOUSTAT_MASK_DOWN = %10000000
MOUSTAT_MASK_WASDOWN = %01000000
MOUSTAT_MASK_MOVED = %00100000
MOUSEMODE_OFF = $00 ; Mouse off
MOUSEMODE_PASSIVE = $01 ; Passive mode (polling only)
MOUSEMODE_MOVEINT = $03 ; Interrupts on movement
MOUSEMODE_BUTINT = $05 ; Interrupts on button
MOUSEMODE_COMBINT = $0f ; Interrupts on VBL, movement and button
; Mouse firmware is all indirectly called, because
; it moved around a lot in different Apple ][ ROM
; versions. This macro helps abstracts this for us.
.macro CALLMOUSE name
ldx #name
jsr WGCallMouse
.endmacro
CH_MOUSEPOINTER = 'B'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGEnableMouse
; Prepares the mouse for use
;
WGEnableMouse:
pha
SETSWITCH PAGE2OFF
; Warning!
; We're relying on the fact that the 65C02 treats undefined opcodes as NOPs here!
; (in the same way that ProDOS 2.4.2 does)
sec
.byte $C2, $01 ;REP #$01 ; clear carry flag on 65816
bcc WGEnableMouse_IIGS
; Find slot number and calculate the various indirections needed
jsr WGFindMouse
bcs WGEnableMouse_Error
; Note if we're a //e or //c, because mouse tracking and interrupts are different
lda $fbb3
cmp #$06
bne WGEnableMouse_Error ; II or II+? Sorry...
lda $fbc0
bne WGEnableMouse_IIe
lda #1
sta WG_APPLEIIC
WGEnableMouse_IIe:
; Install our interrupt handler via ProDOS (play nice!)
jsr PRODOS_MLI
.byte ALLOC_INTERRUPT
.addr WG_PRODOS_ALLOC
bne WGEnableMouse_Error ; ProDOS will return here with Z clear on error
; Initialize the mouse
stz WG_MOUSEPOS_X
stz WG_MOUSEPOS_Y
stz WG_MOUSEBG
CALLMOUSE INITMOUSE
bcs WGEnableMouse_Error ; Firmware sets carry if mouse is not available
CALLMOUSE CLEARMOUSE
lda #MOUSEMODE_COMBINT ; Enable combination interrupt mode
CALLMOUSE SETMOUSE
; Set the mouse's zero postion to (1,1), since we're in text screen space
stz MOUSE_ZEROH
lda #0
sta MOUSE_ZEROL
lda #1
CALLMOUSE CLAMPMOUSE
lda #0
CALLMOUSE CLAMPMOUSE
; Scale the mouse's range into something easy to do math with,
; while retaining as much range of motion and precision as possible
lda WG_APPLEIIC
bne WGEnableMouse_ConfigIIc
lda #$7f ; 640 - 1 horizontally
sta MOUSE_CLAMPL
lda #$02
sta MOUSE_CLAMPH
lda #0
CALLMOUSE CLAMPMOUSE
lda #$e0 ; 736 vertically
sta MOUSE_CLAMPL
lda #$02
sta MOUSE_CLAMPH
lda #1
CALLMOUSE CLAMPMOUSE
bra WGEnableMouse_Activate
WGEnableMouse_IIGS:
lda #$20 ; JSR
sta WGEnableGSMouse
sta WGDisableGSMouse
bra WGEnableMouse_Activate
WGEnableMouse_Error:
stz WG_MOUSEACTIVE
WGEnableMouse_done: ; Exit point here for branch range
pla
rts
WGEnableMouse_ConfigIIc: ; //c's tracking is weird. Need to clamp to a much smaller range
lda #$4f ; 80 - 1 horizontally
sta MOUSE_CLAMPL
lda #$00
sta MOUSE_CLAMPH
lda #0
CALLMOUSE CLAMPMOUSE
lda #$17 ; 24 - 1 vertically
sta MOUSE_CLAMPL
lda #$00
sta MOUSE_CLAMPH
lda #1
CALLMOUSE CLAMPMOUSE
WGEnableMouse_Activate:
lda #1
sta WG_MOUSEACTIVE
cli ; Once all setup is done, it's safe to enable interrupts
bra WGEnableMouse_done
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGDisableMouse
; Shuts off the mouse when we're done with it
;
WGDisableMouse:
pha
SETSWITCH PAGE2OFF
lda WG_MOUSEACTIVE ; Never activated the mouse
beq WGDisableMouse_done
WGDisableGSMouse:
bit GsDisableMouse ; self-modified to JSR when appropriate
lda MOUSEMODE_OFF
CALLMOUSE SETMOUSE
stz WG_MOUSEACTIVE
lda #MOUSEMODE_OFF ; Disable VBL manually
CALLMOUSE SETMOUSE
; Remove our interrupt handler via ProDOS (done playing nice!)
lda WG_PRODOS_ALLOC+1 ; Copy interrupt ID that ProDOS gave us
sta WG_PRODOS_DEALLOC+1
jsr PRODOS_MLI
.byte DEALLOC_INTERRUPT
.addr WG_PRODOS_DEALLOC
jsr WGUndrawPointer ; Be nice if we're disabled during a program
WGDisableMouse_done:
pla
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGCallMouse
; Calls a mouse firmware routine. Here's where we handle all
; the layers of indirection needed to call mouse firmware. The
; firmware moved in ROM several times over the life of the
; Apple ][, so it's kind of a hassle to call it.
; X: Name of routine (firmware offset constant)
; Side effects: Clobbers all registers
WGCallMouse:
stx WGCallMouse+4 ; Use self-modifying code to smooth out some indirection
; This load address is overwritten by the above code, AND by the mouse set
; up code, to make sure we have the right slot entry point and firmware
; offset
ldx $c400 ; Self-modifying code!
stx WG_MOUSE_JUMPL ; Get low byte of final jump from firmware
php ; Note that mouse firmware is not re-entrant,
sei ; so we must disable interrupts inside them
jsr WGCallMouse_redirect
plp ; Restore interrupts to previous state
rts
WGCallMouse_redirect:
ldx WG_MOUSE_JUMPH
ldy WG_MOUSE_SLOTSHIFTED
jmp (WG_MOUSE_JUMPL)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGFindMouse
; Figures out which slot (//e) or port (//c) the mouse is in.
; It moved around a lot over the years. Sets it to 0 if no mouse
; could be found
; OUT C: Set if no mouse could be found
WGFindMouse:
SAVE_AX
ldx #7
WGFindMouse_loop:
txa ; Compute slot firmware locations for this loop
ora #$c0
sta WGFindMouse_loopModify+2 ; Self-modifying code!
sta WGFindMouse_loopModify+9
sta WGFindMouse_loopModify+16
sta WGFindMouse_loopModify+23
sta WGFindMouse_loopModify+30
WGFindMouse_loopModify:
; Check for the magic 5-byte pattern that gives away the mouse card
lda $c005 ; These addresses are modified in place on
cmp #$38 ; each loop iteration
bne WGFindMouse_nextSlot
lda $c007
cmp #$18
bne WGFindMouse_nextSlot
lda $c00b
cmp #$01
bne WGFindMouse_nextSlot
lda $c00c
cmp #$20
bne WGFindMouse_nextSlot
lda $c0fb
cmp #$d6
bne WGFindMouse_nextSlot
bra WGFindMouse_found
WGFindMouse_nextSlot:
dex
bmi WGFindMouse_none
bra WGFindMouse_loop
WGFindMouse_found:
; Found it! Now configure all our indirection lookups
stx WG_MOUSE_SLOT
lda #$c0
ora WG_MOUSE_SLOT
sta WG_MOUSE_JUMPH
sta WGCallMouse+5 ; Self-modifying code!
txa
asl
asl
asl
asl
sta WG_MOUSE_SLOTSHIFTED
clc
bra WGFindMouse_done
WGFindMouse_none:
stz WG_MOUSE_SLOT
sec
WGFindMouse_done:
RESTORE_AX
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGMouseInterruptHandler
; Handles interrupts that may be related to the mouse
; This is a ProDOS-compliant interrupt handling routine, and
; should be installed and removed via ProDOS as needed.
;
; IMPORTANT: This routine is NOT MLI-reentrant, which means MLI
; calls can NOT be made within this handler. See page 108 of the
; ProDOS 8 Technical Reference Manual if this feature needs to be
; added.
;
WGMouseInterruptHandler:
cld ; ProDOS interrupt handlers must open with this
SAVE_AXY
CALLMOUSE SERVEMOUSE
bcc WGMouseInterruptHandler_regard
jmp WGMouseInterruptHandler_disregard
WGMouseInterruptHandler_regard:
php
sei
lda PAGE2 ; Need to preserve text bank, because we may interrupt rendering
pha
SETSWITCH PAGE2OFF
ldx WG_MOUSE_SLOT
lda MOUSTAT,x ; Check interrupt status bits first, because READMOUSE clears them
and #MOUSTAT_MASK_BUTTONINT
bne WGMouseInterruptHandler_button
lda MOUSTAT,x
and #MOUSTAT_MASK_MOVEINT
bne WGMouseInterruptHandler_mouse
jmp WGMouseInterruptHandler_VBL
WGMouseInterruptHandler_mouse:
jsr WGUndrawPointer ; Erase the old mouse pointer
; Read the mouse state. Note that interrupts need to remain
; off until after the data is copied.
CALLMOUSE READMOUSE
ldx WG_MOUSE_SLOT
lda MOUSTAT,x ; Movement/button status bits are now valid
sta WG_MOUSE_STAT
lda WG_APPLEIIC
bne WGMouseInterruptHandler_IIc
; Read mouse position and transform it into screen space
lsr MOUSE_XH,x
ror MOUSE_XL,x
lsr MOUSE_XH,x
ror MOUSE_XL,x
lsr MOUSE_XH,x
ror MOUSE_XL,x
lda MOUSE_XL,x
sta WG_MOUSEPOS_X
lsr MOUSE_YH,x
ror MOUSE_YL,x
lsr MOUSE_YH,x
ror MOUSE_YL,x
lsr MOUSE_YH,x
ror MOUSE_YL,x
lsr MOUSE_YH,x
ror MOUSE_YL,x
lsr MOUSE_YH,x
ror MOUSE_YL,x
lda MOUSE_YL,x
sta WG_MOUSEPOS_Y
bra WGMouseInterruptHandler_draw
WGMouseInterruptHandler_IIc: ; IIc tracks much slower, so don't scale
lda MOUSE_XL,x
sta WG_MOUSEPOS_X
lda MOUSE_YL,x
sta WG_MOUSEPOS_Y
WGMouseInterruptHandler_draw:
jsr WGDrawPointer ; Redraw the pointer
bra WGMouseInterruptHandler_intDone
WGMouseInterruptHandler_disregard:
; Carry will still be set here, to notify ProDOS that
; this interrupt was not ours
RESTORE_AXY
rts
WGMouseInterruptHandler_button:
CALLMOUSE READMOUSE
ldx WG_MOUSE_SLOT
lda MOUSTAT,x ; Movement/button status bits are now valid
sta WG_MOUSE_STAT
bit WG_MOUSE_STAT ; Check for rising edge of button state
bpl WGMouseInterruptHandler_intDone
lda WG_MOUSE_BUTTON_DOWN
bne WGMouseInterruptHandler_intDone
WGMouseInterruptHandler_buttonDown:
; Button went down, so make a note of location for later
lda #1
sta WG_MOUSE_BUTTON_DOWN
lda WG_MOUSEPOS_X
sta WG_MOUSECLICK_X
lda WG_MOUSEPOS_Y
sta WG_MOUSECLICK_Y
WGMouseInterruptHandler_intDone:
pla ; Restore text bank
bpl WGMouseInterruptHandler_intDoneBankOff
SETSWITCH PAGE2ON
bra WGMouseInterruptHandler_done
WGMouseInterruptHandler_VBL:
CALLMOUSE READMOUSE
ldx WG_MOUSE_SLOT
lda MOUSTAT,x ; Movement/button status bits are now valid
sta WG_MOUSE_STAT
bmi WGMouseInterruptHandler_intDone
stz WG_MOUSE_BUTTON_DOWN
bra WGMouseInterruptHandler_intDone
WGMouseInterruptHandler_intDoneBankOff:
SETSWITCH PAGE2OFF
WGMouseInterruptHandler_done:
RESTORE_AXY
plp
clc ; Notify ProDOS this was our interrupt
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGUndrawPointer
; Unplots the mouse pointer at current location
; Side effects: Clobbers BASL,BASH
;
WGUndrawPointer:
pha
lda #$80
jsr renderPointer
pla
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGDrawPointer
; Plots the mouse pointer at current location
;
WGDrawPointer:
pha
lda #0
jsr renderPointer
pla
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WGPointerDirty
; Updates the background behind the mouse pointer without
; modifying it's current render state. Assumes pointer is not
; currently visible
;
WGPointerDirty:
pha
lda #%11000000
jsr renderPointer
pla
rts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; renderPointer
; Performs mouse-pointer-related rendering
; A: 0=draw, %10000000=undraw, %11000000=recapture background
;
renderPointer:
SAVE_AXY
sta renderPointerMode
lda BASL ; Need to preserve BAS, because we may interrupt rendering
pha
lda BASH
pha
lda PAGE2 ; Need to preserve text bank, because we may interrupt rendering
pha
lda WG_MOUSEACTIVE
beq renderPointer_done ; Mouse not enabled
ldx WG_MOUSEPOS_Y
cpx #24
bcs renderPointer_done ; Mouse out of range (vertically)
lda TEXTLINES_L,x ; Compute video memory address of point
sta BASL
lda TEXTLINES_H,x
sta BASH
lda WG_MOUSEPOS_X
cmp #80
bcs renderPointer_done ; Mouse out of range (horizontally)
lsr
clc
adc BASL
sta BASL
lda #0
adc BASH
sta BASH
lda WG_MOUSEPOS_X ; X even?
ror
bcs renderPointer_xOdd
SETSWITCH PAGE2ON
bit renderPointerMode ; Draw or undraw?
bpl renderPointer_draw
bvc renderPointer_undraw
renderPointer_draw:
lda (BASL) ; Save background
cmp #CH_MOUSEPOINTER ; Make sure we never capture ourselves and leave a "stamp"
beq renderPointer_drawSaved
sta WG_MOUSEBG
renderPointer_drawSaved:
bit renderPointerMode ; Recapture or draw?
bvs renderPointer_done
lda #CH_MOUSEPOINTER ; Draw the pointer
sta (BASL)
bra renderPointer_done
renderPointer_undraw:
lda WG_MOUSEBG
beq renderPointer_done ; No saved background yet
sta (BASL)
bra renderPointer_done
renderPointer_xOdd:
SETSWITCH PAGE2OFF
bit renderPointerMode ; Draw or undraw?
bpl renderPointer_drawOdd
bvc renderPointer_undraw
renderPointer_drawOdd:
lda (BASL) ; Save background
cmp #CH_MOUSEPOINTER ; Make sure we never capture ourselves and leave a "stamp"
beq renderPointer_drawOddSaved
sta WG_MOUSEBG
renderPointer_drawOddSaved:
bit renderPointerMode ; Recapture or draw?
bvs renderPointer_done
lda #CH_MOUSEPOINTER ; Draw the pointer
sta (BASL)
bra renderPointer_done
renderPointer_done:
pla ; Restore text bank
bpl renderPointer_doneBankOff
SETSWITCH PAGE2ON
bra renderPointer_doneBAS
renderPointer_doneBankOff:
SETSWITCH PAGE2OFF
renderPointer_doneBAS:
pla ; Restore BAS
sta BASH
pla
sta BASL
RESTORE_AXY
rts
renderPointerMode:
.byte 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Mouse API state
;
WG_MOUSEACTIVE:
.byte 0
WG_MOUSE_STAT:
.byte 0
WG_MOUSEBG:
.byte 0
WG_APPLEIIC:
.byte 0
WG_MOUSE_JUMPL:
MousePixX: ;(GS-overload) Mouse pixel position 0-639 (word)
.byte 0
WG_MOUSE_JUMPH:
.byte 0
WG_MOUSE_SLOT:
MousePixY: ;(GS-overload) Mouse pixel position 0-191 (word)
.byte 0
WG_MOUSE_SLOTSHIFTED:
.byte 0
WG_MOUSE_BUTTON_DOWN:
MouseBtn0: ;(GS-overload) Previous btn0 state used for edge detect
.byte 0
WG_MOUSEPOS_X:
MouseTxtX: ;(GS-overload) Mouse text position 0-79
.byte 39
WG_MOUSEPOS_Y:
MouseTxtY: ;(GS-overload) Mouse text position 0-23
.byte 11
WG_MOUSECLICK_X:
MouseDownX: ;(GS-overload) Mouse click pos 0-79
.byte $ff
WG_MOUSECLICK_Y:
MouseDownY: ;(GS-overload) Mouse click pos 0-23
.byte 0
MouseMaxX:
.word 80*8 ;(GS-extension) Mouse max X = 640
MouseMaxY:
.word 24*8 ;(GS-extension) Mouse max Y = 192
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ProDOS system call parameter blocks
;
WG_PRODOS_ALLOC:
.byte 2
.byte 0 ; ProDOS returns an ID number for the interrupt here
.addr WGMouseInterruptHandler
WG_PRODOS_DEALLOC:
.byte 1
.byte 0 ; To be filled with ProDOS ID number