*******************************************************************************
*
* BoxCtl CDEFProc -- Version 3.0
*
* (C)  Copyright Apple Computer, Inc. 1988-1990
* All rights reserved.
*
* Developer Technical Support Apple II Sample Code
*
* by Keith Rollin
*
*******************************************************************************
*
myDragCtl           start
*
* Description:      Drag command. We hit something. Determine what it was and
*                   drag it around the screen. If we hit the frame, then drag
*                   the whole control. If we hit a knob, then we grow the
*                   control (just like growing a window).
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:
*                   Import SetMyPen
*                   Import Snap2Grid
*                   Import myDrawCtl
*                   Import FindPart
*                   Import SetCtlRect
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    PushLong #oldPenState ; save drawing state, as we change it
                    _GetPenState

                    ldy #6              ; make a working copy of CtlRect.
loop100             lda myCtlRect,y
                    sta drag_rect,y
                    dey
                    dey
                    bpl loop100

                    pha                 ; has the mouse been lifted up?
                    PushWord #mUpMask   ; ask for any such events
                    PushLong #TrackEvent
                    _GetNextEvent
                    pla

                    lda TrackEvent+owhat
                    cmp #mouseUpEvt     ; was there a mouse up event?
                    bne mDown           ; no - so start tracking
                    brl done            ; yes - so leave

mDown               ANOP
                    PushLong #TrackEvent+owhere
                    _GlobalToLocal

                    lda TrackEvent+owhere
                    sta OldMouse        ; FindPart likes the mouse Position
                    sta theParam        ; in 'theParam'
                    lda TrackEvent+owhere+2
                    sta OldMouse+2
                    sta theParam+2

                    jsr FindPart        ; what did we hit? (returns internal
                    sta dragPart        ;   partcode number).

                    cmp #InteriorPart+1 ; what part did we hit?
                    bge GrowFrame       ; some knob - grow the control
                    brl DragFrame       ; interior or frame - move the control

;
; This part of the program is called when we detect that we have clicked on
; a grow knob. It tracks the motions of the mouse, and grows/draws the
; control appropriately.
;
GrowFrame           ANOP
                    PushWord #2         ; set us to XOR mode. This is for the
                    _SetPenMode         ; rubber-banding effect of growing.

                    jsr SetMyPen        ; set my pen's width

                    PushLong #GreyPattern ; grow with a grey pattern
                    _SetPenPat

                    ldy #oCtlMaxX       ; get local copies of the maximum
                    ldx #6              ; and minimum dimensions.
loop0010            lda [<CtlPtr],y
                    sta MinY,x
                    dey
                    dey
                    dex
                    dex
                    bpl loop0010

; validate the min and max values. the min values must be at least $10.
; if not, they are set there. the max values must be at least as large
; as the min values. if not, they are set to $FFFF

                    lda #$10
                    cmp MinX
                    blt ckMinY
                    sta MinX

ckMinY              cmp MinY
                    blt ckMaxX
                    sta MinY

ckMaxX              lda MaxX
                    cmp MinX
                    bge ckMaxY
ChgX                lda #$FFFF
                    sta MaxX

ckMaxY              lda MaxY
                    cmp MinY
                    bge doneCk
ChgY                lda #$FFFF
                    sta MaxY
doneCk              ANOP

;
; We grow the control with the following algorithm. First, setup by:
;
;  1. saving the current mouse postion in 'NewMouse',
;  2. aligning 'NewMouse' to the grid, and
;  3. making a working copy of the control's rectangle in 'drag_rect'.
;
; We then enter the main loop:
;
;  4. Draw a copy of the rubber-banding rectangle.
;  5. Check the mouse button. If it is up, goto #12
;  6. Put the position of the mouse into 'NewerMouse'. Align it to grid.
;  7. Check it against 'NewMouse'. If it hasn't changed, goto #5.
;  8. Erase the old rubber-Band.
;  9. Calculate the new rectangle and put it into 'drag_rect'
; 10. Move 'NewerMouse' into 'NewMouse'
; 11. Goto #4
;
; The growing is all done. Clean up and leave.
;
; 12. Erase the rubber band.
; 13. Erase the control.
; 14. Invalidate the area under the control
; 15. Make drag_rect the new boundary of the control
; 16. Say goodnite, Gracie.
;

                    lda OldMouse
                    sta NewMouse
                    lda OldMouse+2
                    sta NewMouse+2

                    ldx #^NewMouse      ; make sure we are on the grid!!!
                    lda #NewMouse
                    jsr Snap2Grid

DrawLoop            ANOP
                    PushLong #drag_rect ; draw the rubber-band
                    _FrameRect

WaitLoop            ANOP
                    pha                 ; has the mouse been lifted up?
                    PushWord #mUpMask   ; ask for any such events
                    PushLong #TrackEvent
                    _GetNextEvent
                    pla

                    lda TrackEvent+owhat
                    cmp #mouseUpEvt
                    beq done            ; mouse up occured - so leave.
                    
                    lda TrackEvent+owhere ; mouse still down. copy it to
                    sta NewerMouse      ; NewerMouse
                    lda TrackEvent+owhere+2
                    sta NewerMouse+2
                    
                    PushLong #NewerMouse ; convert it to window coordinates.
                    _GlobalToLocal

                    ldx #^NewerMouse    ; make sure the new position is on
                    lda #NewerMouse     ; the grid.
                    jsr Snap2Grid

                    lda NewerMouse      ; See if the mouse position moved. We
                    cmp NewMouse        ; only redraw the rubber-band if it did.
                    bne ReDraw          ; It did - redraw the rubber-band
                    lda NewerMouse+2
                    cmp NewMouse+2
                    beq WaitLoop        ; No movement - check button state

ReDraw              ANOP
                    PushLong #drag_rect ; erase the old rubber band
                    _FrameRect

                    lda NewerMouse      ; copy NewerMouse for next loop
                    sta NewMouse
                    sec                 ; and update deltaXY for AdjFrameRect
                    sbc OldMouse
                    sta deltaY

                    lda NewerMouse+2
                    sta NewMouse+2
                    sec
                    sbc OldMouse+2
                    sta deltaX

                    jsr AdjFrameRect    ; adjust for drawing the new rect

                    brl DrawLoop        ; redraw the rubber-band

done                ANOP
                    PushLong #drag_rect ; erase the rubber band
                    _FrameRect

                    PushLong #myCtlRect ; erase the control
                    _EraseRect

                    PushLong #myCtlRect ; invalidate the stuff under it
                    _InvalRect

                    brl Exit
;
; This part of the program is called when we have clicked on either the
; frame or the interior. It calls dragrect to move an outline of the
; control around.
;

DragFrame           ANOP

; Get the portRect of the window that owns this control. This rectangle
; will be used to control the limits of dragging.

                    ldy #octlOwner      ; get the pointer to the control's
                    lda [<CtlPtr],y     ; owning window into 'work'
                    sta <work
                    iny
                    iny
                    lda [<CtlPtr],y
                    sta <work+2

                    ldy #oportRect+6    ; copy the PortRect of the window
                    ldx #6              ; out of its GrafPort into limitRect.
loop400             lda [<work],y
                    sta limitRect,x
                    dey
                    dey
                    dex
                    dex
                    bpl loop400

;
; Use dragRect to drag an outline of the control around the
; screen for me.
;
                    pha                 ; space for result
                    pha
                    PushLong #DragDraw  ; no action proc
                    PushLong #GreyPattern ; drag pattern
                    PushLong OldMouse   ; starting location of mouse
                    PushLong #drag_rect ; outline to drag
                    PushLong #limitRect ; limitRect
                    PushLong #limitRect ; slopRect
                    PushWord #%00101000 ; custom drag flag, ReturnRect flag
                    _DragRect

                    pla                 ; pull off and discard the deltas
                    pla

                    PushLong #myCtlRect ; erase the control in its old pos'n
                    _EraseRect

                    PushLong #myCtlRect ; invalidate the stuff under it
                    _InvalRect

                    ldx #^drag_rect     ; align the new rectangle to the grid.
                    lda #drag_rect
                    jsr Snap2Grid

Exit                ANOP
                    ldy #6              ; copy over the new control frame
loop200             lda drag_rect,y
                    sta myCtlRect,y
                    dey
                    dey
                    bpl loop200

                    PushLong #myCtlRect ; Invalidate the new area under the
                    _InvalRect          ; control so it will be redrawn.

                    PushLong #oldPenState
                    _SetPenState        ; clean up QuickDraw after ourselves.

                    jsr SetCtlRect      ; copy new rect into control record

                    lda #-1             ; Say that we handled it
                    tax

                    rts

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Called by DragRect to draw the rectangle of the control. I draw the outline
; myself so that I can align it to my own grid. The normal aligning of the
; rectangle to the grid only aligns to powers of 2. I align to anything.
;
; When this routine is called, the stack looks like this:
;
;                   PUSH:WORD - delta X
;                   PUSH:WORD - delta Y
;                   PUSH:BYTE[3] - return address
;
; However, since I set the ReturnRect flag, I can ignore the deltas passed to
; me, and use drag_rect directly. I make a copy of it, make sure that it is
; aligned to the grid, draw it, remove the parameters, and return to the
; control manager.
;

DragDraw            ANOP

RTLAddr             equ 1
DY                  equ RTLAddr+3
DX                  equ DY+2

                    ldx #6              ; make a copy of the rectangle that
loop500             lda drag_rect,x     ; _DragRect is passing to us. We are
                    sta draw_rect,x     ; going to modify it so that it is
                    dex                 ; aligned to the grid.
                    dex
                    bpl loop500

                    ldx #^draw_rect     ; align the new rectangle to the grid.
                    lda #draw_rect
                    jsr Snap2Grid

DrawOutline         ANOP
                    PushLong #draw_rect
                    _FrameRect

ExitDragDraw        ANOP
                    lda 1,s             ; move the RTL address back up
                    sta 5,s
                    lda 2,s
                    sta 6,s

                    pla                 ; remove the parameters (DX, DY)
                    pla

                    rtl                 ; back to dragRect!!

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Called by the routine that grows the control with a rubber-band
; sort of thing. It uses the part code we hit as an index into
; a table. This table contains a list of offsets into the
; 4 words of drag_rect. It reads in a pair of these offsets and
; uses them to modify the appropriate X/Y coordinates with
; deltaY and deltaX. DeltaY and deltaX are set up by the
; rubber-banding routine.

AdjFrameRect        ANOP
                    ldx #6
loop380             lda drag_rect,x
                    sta new_rect,x
                    dex
                    dex
                    bpl loop380

                    lda dragPart        ; get the part code we are dragging
                    sec
                    sbc #ULPart
                    asl a               ; turn it into an index.
                    asl a
                    tax
                    lda PtToAdj,x       ; get Y coordinate to adjust
                    tay
                    lda PtToAdj+2,x     ; get X coordinate to adjust
                    tax

; if the entry that was fetched from the table was -1, then that means
; that we are making no adjustments in that direction. For instance, it
; we are dragging on the top edge knob, we want to adjust just the top
; side of the rectangle, but want to leave either of the sides alone.

                    cpy #-1             ; modify the vertical coordinate
                    beq NoChg1          ; of choice if necessary.
                    clc
                    lda deltaY
                    adc myCtlRect,y
                    sta new_rect,y

NoChg1              cpx #-1             ; modify the horizontal coordinate
                    beq NoChg2          ; of choice if necessary.
                    clc
                    lda deltaX
                    adc myCtlRect,x
                    sta new_rect,x

NoChg2              ANOP
                    ldx #^new_rect      ; align the rectangle to the grid.
                    lda #new_rect
                    jsr Snap2Grid

; now that we have a new rectangle, we have to make sure that it is within
; the maximum and minimum sizes allowed.

                    sec                 ; make sure the new Y size is OK
                    lda new_rect+4
                    sbc new_rect
                    bcc NoCopy1
                    cmp MinY
                    blt NoCopy1         ; it's not, so leave old coords there
                    inc a
                    cmp MaxY
                    bge NoCopy1         ; it's not, so leave old coords there

                    lda new_rect        ; it is, so update it.
                    sta drag_rect
                    lda new_rect+4
                    sta drag_rect+4

NoCopy1             ANOP
                    sec                 ; make sure the new X size is OK
                    lda new_rect+6
                    sbc new_rect+2
                    bcc NoCopy2
                    cmp MinX
                    blt NoCopy2         ; it's not, so leave old coords there
                    inc a
                    cmp MaxX
                    bge NoCopy2         ; it's not, so leave old coords there

                    lda new_rect+2      ; it is, so update it
                    sta drag_rect+2
                    lda new_rect+6
                    sta drag_rect+6
NoCopy2             ANOP
                    rts


PtToAdj             dc i2'0,2,0,-1,0,6,-1,6,4,6,4,-1,4,2,-1,2'

TrackEvent          ds $10

OldMouse            ds 4
NewMouse            ds 8
NewerMouse          ds 8
drag_rect           ds 8
draw_rect           ds 8
new_rect            ds 8
limitRect           ds 8
MinY                ds 2
MinX                ds 2
MaxY                ds 2
MaxX                ds 2

                    end

                    EJECT
*******************************************************************************
*
myNewValue          start
*
* Description:      Called when the value of the control is to be changed.
*                   The area under the control is invalidated so that it will
*                   be redrawn.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:
*                   Import Snap2Grid
*                   Import myDrawCtl
*                   Import SetCtlRect
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    PushLong #myCtlRect ; erase old control
                    _EraseRect

                    PushLong #myCtlRect ; invalidate area erased
                    _InvalRect

                    ldx #^myCtlRect     ; make sure control is on the grid
                    lda #myCtlRect
                    jsr Snap2Grid

                    jsr SetCtlRect

                    lda #0              ; return no value
                    tax

                    rts
                    end

                    EJECT
*******************************************************************************
*
mySetParams         start
*
* Description:      Set new parameters command. Don't set if either of them
*                   are negative.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    lda theParam        ; these have gotta go on in reverse
                    sta <work+2
                    lda theParam+2
                    sta <work

                    ora <work+2         ; is this a NULL pointer?
                    bne CopyIt          ; no - so use it

                    lda #^StdCtlData    ; yes - so set pointer to default data
                    sta <work+2
                    lda #StdCtlData
                    sta <work

CopyIt              ANOP
                    ldy #oCtlSize-2     ; copy the data into control record.
                    ldx #oCtlSize-oCtlMinY-2
loop                phy
                    txy
                    lda [<work],y
                    ply
                    sta [<CtlPtr],y
                    dey
                    dey
                    dex
                    dex
                    bpl loop

                    lda #0              ; return no value
                    tax

                    rts
                    end

                    EJECT
*******************************************************************************
*
myRecSize           start
*
* Description:      Return record size command.
*
*
* Inputs:           NONE
*
* Outputs:          A = Size of the control record to allocate.
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    lda #oCtlSize       ; low word of record size
                    ldx #0              ; high word

                    rts
                    end

                    EJECT
*******************************************************************************
*
Null                start
*
* Description:      Null routine. Does nothing. Returns no error.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************

                    lda #0
                    tax

                    rts
                    end

                    EJECT
*******************************************************************************
*
InitCtlData         start
*
* Description:      Initialize some data that will needs to be handy: Get a
*                   pointer to the control record (we are supplied only with
*                   a handle), make a local copy of the control rectangle, set
*                   up a pointer to a color table, and move CtlParam from a
*                   Direct Page location into an absolute location.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

;
; Check to see if the Control Manager is sending us a 'RecSize' call. If it
; is, then we cannot rely on the validity of the Control handle, as it has not
; yet been allocated and is essentially garbage. Since we do not know what it
; points to, we don't want to read from it, as it may actually accidentally
; point to something dangerous, like the IWM I/O locations!!! So if we detect
; a RecSize code, skip over this routine.
;
                    lda <CtlCode        ; is this a RecSize call?
                    cmp #recSize
                    beq done            ; yes - skip this whole routine

;
; Copy the value in CtlParam into a local location -- off of the direct page.
; We do this for convenience. We may need to pass a pointer to CtlParam to
; a toolbox routine, but you can't do that for a direct page location. The
; number assiciated with a direct page location is really an offset off of the
; base specified by the Direct Page register; it is NOT an absolute memory
; location. So that we can conveniently use 'PushLong #location' if we need
; to, I copy CtlParam to a non-direct page location.
;
                    lda <CtlParam
                    sta theParam
                    lda <CtlParam+2
                    sta theParam+2

                    ldy #2              ; get a handy pointer to the CtlRec
                    lda [<theCtlHandle],y
                    sta <CtlPtr+2
                    lda [<theCtlHandle]
                    sta <CtlPtr

                    ldy #octlRect+6     ; copy the control rect over to the
                    ldx #6              ; local variable.
loop                lda [<CtlPtr],y
                    sta myCtlRect,x
                    dey
                    dey
                    dex
                    dex
                    bpl loop

;
; The following values are used by Snap2Grid. However, Snap2Grid can be
; called with an invalid Direct Page, so we copy them out the control
; record (which is pointed to by a pointer in our Direct Page) and into
; some absolute locations that we can get to at all times.
;
                    ldy #oCtlGridX      ; get the X and Y spacings out of the
                    lda [<CtlPtr],y     ; control record and into some local
                    sta myCtlGridX      ; storage for easy handling.

                    ldy #oCtlGridY
                    lda [<CtlPtr],y
                    sta myCtlGridY

; Check the color table pointer. If it is non-zero, install it as the
; pointer to the color table we'll be using. If not, then install a
; pointer to a default color table.

                    ldy  #octlColor     ; Get the pointer to the color
                    lda [<CtlPtr],y     ; table in the control record.
                    sta ColorPtr        ; Save it to a local pointer.
                    iny
                    iny
                    lda [<CtlPtr],y
                    sta ColorPtr+2
                    ora ColorPtr        ; is it a NULL pointer?
                    bne done            ; no, so use it.

                    lda #StdColorTable  ; yes, install my own color table.
                    sta ColorPtr
                    lda #^StdColorTable
                    sta ColorPtr+2

done                ANOP
                    rts

                    end

                    EJECT
*******************************************************************************
*
SetCtlRect          start
*
* Description:      This routine is called when the control's bounding
*                   rectangle has been changed and needs to be written back
*                   out to the control record.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    ldy #octlRect+6     ; get an offset into the control rec.
                    ldx #6              ; index to my local record.
loop                ANOP
                    lda myCtlRect,x
                    sta [<CtlPtr],y
                    dey
                    dey
                    dex
                    dex
                    bpl loop

                    rts

                    end

                    EJECT
*******************************************************************************
*
SetMyPen            start
*
* Description:      We want our control to look good in both 320 and 640 mode.
*                   Because vertical lines are thinner in 640 than 320, its a
*                   good idea to draw them doubly thick just so they can be
*                   seen. This routine will determine what mode we are in and
*                   adjust the pen accordingly.
*
*
* Inputs:           NONE
*
* Outputs:          NONE
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

                    pha                 ; we base pen width on the MasterSCB
                    _GetMasterSCB
                    pla
                    and #$0080          ; check the mode bit
                    beq pen320          ; we are in 320 mode - go push a 1

                    PushWord #2         ; in 640 mode - push on a 2 width
                    bra setpn

pen320              PushWord #1         ; in 320 mode - push on a 1 width

setpn               ANOP                ; width is 1 or 2
                    PushWord #1         ; Height is always 1
                    _SetPenSize

                    rts
                    end

                    EJECT
*******************************************************************************
*
CalcCorners         start
*
* Description:      This routine calculates the rectangles for all of the
*                   little knobs that are used to grow the control. They are
*                   stored locally - not with the control record - and so need
*                   to be calculated every time we need to check for hits or
*                   to draw the control.
*
*
* Inputs:           NONE
*
* Outputs:          knobXXX, FrameHeight, FrameWidth variables are initialized.
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

top                 equ 0               ; alias these to something a little
left                equ 2               ; more descriptive.
bottom              equ 4
right               equ 6

; Get the size of the knobs. The height and width are stored in the CtlValue
; field in a packed format. This unpacks them and stores them locally for
; ease of access.

                    ldy #octlValue
                    lda [<CtlPtr],y
                    and #$00FF
                    sta FrameHeight
                    lda [<CtlPtr],y
                    xba
                    and #$00FF
                    sta FrameWidth

;
; We "assembly line" the initialization of the knob rectangles. Instead
; of calculating just one knob at a time, we do a few of them all at the
; same time. Here we initialize the tops and bottoms of the 3 top knobs.

                    lda myCtlRect+top
                    sta knobUL+top
                    sta knobTop+top
                    sta knobUR+top
                    clc
                    adc FrameHeight
                    sta knobUL+bottom
                    sta knobTop+bottom
                    sta knobUR+bottom

;
; Initialize the lefts and rights of the three left knobs.
;
                    lda myCtlRect+left
                    sta knobUL+left
                    sta knobLeft+left
                    sta knobLL+left
                    clc
                    adc FrameWidth
                    sta knobUL+right
                    sta knobLeft+right
                    sta knobLL+right

;
; Initialize the tops and bottoms of the three bottom knobs.
;
                    lda myCtlRect+bottom
                    sta knobLL+bottom
                    sta knobBottom+bottom
                    sta knobLR+bottom
                    sec
                    sbc FrameHeight
                    sta knobLL+top
                    sta knobBottom+top
                    sta knobLR+top

;
; Initialize the lefts and rights of the three right knobs.
;
                    lda myCtlRect+right
                    sta knobUR+right
                    sta knobRight+right
                    sta knobLR+right
                    sec
                    sbc FrameWidth
                    sta knobUR+left
                    sta knobRight+left
                    sta knobLR+left

;
; At this point, the 4 corner knobs have been completely initialized.
; If we are going to be using the edge knobs, then we'll have to do
; some extra math. However, if we aren't, then we'll skip over the
; math.

                    ldy #octlFlag
                    lda [<CtlPtr],y
                    and #knobEdgeMask
                    beq done

; Do extra setup for edge knobs
;
; First, get top and bottom coordinates for the side edges:
;
;     top = (myCtlRect.top + myCtlRect.bottom - FrameHeight)/2
;     bot = (myCtlRect.top + myCtlRect.bottom + FrameHeight)/2

                    clc
                    lda myCtlRect+top
                    adc myCtlRect+bottom
                    pha
                    adc FrameHeight
                    lsr a
                    sta knobLeft+bottom
                    sta knobRight+bottom
                    pla
                    sec
                    sbc FrameHeight
                    lsr a
                    sta knobLeft+top
                    sta knobRight+top

;
; Now perform similar calculations for the left and right sides
; of the top and bottom edge knobs.
;
                    clc
                    lda myCtlRect+left
                    adc myCtlRect+right
                    pha
                    adc FrameWidth
                    lsr a
                    sta knobTop+right
                    sta knobBottom+right
                    pla
                    sec
                    sbc FrameWidth
                    lsr a
                    sta knobTop+left
                    sta knobBottom+left

done                ANOP
                    rts
                    end

                    EJECT
*******************************************************************************
*
Snap2Grid           start
*
* Description:      This control defproc lets the application optionally create
*                   an invisible grid based on local coordinates. When this is
*                   done, the 4 corners of the control are always aligned to
*                   the coordinates of this grid. This occurs when we drag or
*                   resize the control, or call MoveControl. Snap2Grip is the
*                   routine that ensures we are on the grid.
*
*
* Inputs:           A = low word of pointer to rectangle to fix
*                   X = high word
*
* Outputs:          rectangle is modified in place.
*
* External Refs:    NONE
*
* Entry Points:     NONE
*
*******************************************************************************
                    using CtlData

oldD                equ 1
oldB                equ oldD+2
rectPtr             equ oldB+1

;
; We are going to use a local direct page here. This is because Snap2Grid is
; called by my routine that draws the control's recangle as it is being
; dragged. When that happens, we are not using our normal direct page, and
; hence can't use any of the values or work locations there. So we set up
; another one with it's own workspace.
;
                    phx                 ; save the pointer to the rectangle
                    pha

                    phb                 ; save the caller's data bank register
                    phd                 ; save the caller'd direct page reg.

                    phk                 ; switch data bank to program bank
                    plb

                    tsc                 ; switch direct page to stack pointer
                    tcd


;
; WhchGridYX is used to help generalize this routine. We support different
; spacings in the X and Y directions of the grid. As we loop through all 4
; coordinates of the rectangle, we want to make sure that we are using the
; correct spacing. 'whichGridYX' holds flags that tell us when to use X and
; when to use Y spacing (0 = use Y spacing; 1 = use X spacing).
;
                    lda #%0101          ; Set up some indices into GridYX
                    sta whichGridYX

;
; Fix the coordinates by using the following method:
;
;  1. Get the difference between the coordinate we are examining and
;     the next lowest grid location:
;
;          z = coordinate mod GridSize
;
;  2. Z tells how far away from the next lowest grid line
;     the coordinate we are looking at is, so subtracting that
;     value will put the coordinate on the grid.
;
;          coordinate := coordinate - z
;
;  3. However, this is simply truncating, and I don't like the
;     way that feels when resizing the control. So lets add an
;     instruction so that it ROUNDS instead.
;
;          if z > GridSize/2 then
;               coordinate := coordinate + GridSize
;

                    ldy #6              ; init our loop index
Loop                ANOP
                    sty YSave

                    lda whichGridYX     ; do we now use X or Y grid spacing?
                    and #%0001
                    beq UseY
                    lda myCtlGridX
                    bra UseIt
UseY                ANOP
                    lda myCtlGridY

; we now have the gridsize in the Acc
UseIt               ANOP
                    cmp #2              ; Is it 0 or 1? If so, then skip over
                    blt ShortCircuit    ;   dividing routine.
                    sta GridSize        ; save this for the divide later.

                    pha                 ; push space on stack for the result
                    pha                 ; of the divide we are going to do.

                    lda [<rectPtr],y    ; push on the coordinate we are
                    pha                 ;   adjusting as the Dividend
                    PushWord GridSize   ; push this on as the divisor
                    _UDivide            ; unsigned divide
                    pla                 ; quotient - we don't want it.
                    PullWord Remainder  ; remainder is 'z' - Keep it!

                    ldy YSave           ; get my coordinate index back.
                    lda [<rectPtr],y    ; get the coordinate to change
                    sec
                    sbc Remainder       ; change it
                    sta [<rectPtr],y    ; save it back out

                    lda GridSize        ; Make a GridSize/2
                    lsr a
                    cmp Remainder       ; is remainder < GridSize/2?
                    bge ShortCircuit    ; yes - so we're done
                    lda [<rectPtr],y    ; no - so bump the coordinate
                    clc
                    adc GridSize
                    sta [<rectPtr],y

ShortCircuit        lsr whichGridYX     ; move the next index into place
                    dey                 ; bump down to the next coordinate
                    dey                 ;   of the rectangle.
                    bpl Loop

Exit                ANOP
                    pld                 ; restore caller's Direct Page reg.
                    plb                 ; restore data bank register
                    pla                 ; remove rect pointer
                    pla
                    rts                 ; all done - go bye-bye

whichGridYX         ds 2
YSave               ds 2
Remainder           ds 2
GridSize            ds 2

                    end

                    EJECT
*******************************************************************************
*
FindPart            start
*
* Description:      This routine is called to find out exactly which part of
*                   the control we clicked in. It first checks to see if we
*                   clicked in any of the grow knobs. If so, then it returns
*                   an 'internal partcode' telling the calling routine which
*                   knob was clicked in. If a knob wasn't clicked in, a check
*                   is made to see if we clicked on the frame. If we are
*                   allowed to click in the interior, a check is made to see
*                   if we clicked there, too.
*
*
* Inputs:           NONE
*
* Outputs:          A = internal partcode of part hit (1-10)
*
* External Refs:
*                   Import CalcCorners
*
* Entry Points:
*                   entry TestFrame     ; called by myTestCtl
*
*******************************************************************************
                    using CtlData

                    jsr CalcCorners

;
; We first check to see if the mouse was clicked in any of the knobs. We do
; this by assuming a knob was clicked in, and then checking to see if that was
; true. If so, we leave here with the partcode we assumed. If not, we loop
; through and check all of the other knobs. If none of the knobs was clicked
; in, we see if the frame was clicked on.

                    ldx #LeftPart       ; get last knob's part code
                    ldy #7*8            ; point to last knob's rect

loop                ANOP
                    phx                 ; save partcode we're checking for...
                    phy                 ; and the rect's pointer

                    pha                 ; space for _PtInRect result
                    PushLong #theParam  ; push on the point to check
                    lda #^knobUL        ; create a pointer to the rect
                    pha
                    clc                 ; Bumping just the low byte is OK
                    tya                 ;   unless the GS suddenly lets
                    adc #knobUL         ;   code segments cross bank
                    pha                 ;   boundaries
                    _PtInRect
                    pla                 ; get result of PtInRect
                    ply                 ; retrieve rectangle index
                    plx                 ; retrieve part code
                    cmp #0              ; did we score a hit?
                    bne InKnob          ; yes, return with the part code
                    dex                 ; move down to next partcode
                    sec                 ; move down to next rectangle
                    tya
                    sbc #8
                    tay
                    bpl loop            ; keep going until rect index < 0

                    bra TestFrame       ; not in knobs - try frame

;
; We just scored a hit on one of the knobs, and X holds the knob number.
; However, the application may not have corner or edge knobs active. So
; if we clicked on an inactive knob, have to check for that and return
; 'inFrame' instead of a knob partcode.
;
InKnob              ANOP
                    ldy #octlFlag       ; are corner knobs active?
                    lda [<CtlPtr],y
                    and #knobCornerMask
                    bne ckEdge          ; yes - so pass this code on.

                    txa                 ; Corners not allowed. Did we hit one?
                    lsr a
                    bcs RetFramePart    ; yes, so return click on Frame instead

ckEdge              ANOP
                    ldy #octlFlag       ; are edge knobs active?
                    lda [<CtlPtr],y
                    and #knobEdgeMask
                    bne done            ; yes - so return this code

                    txa                 ; Edges not allowed. Did we hit one?
                    lsr a
                    bcs TestFrame       ; no, so pass this code on.
RetFramePart        ANOP
                    ldx #FramePart      ; yes, so return click on Frame instead
done                ANOP
                    txa                 ; put the part code in the Acc
                    rts

TestFrame           entry

; First test to see if it is within the Control's rectangle. It should
; be, but let's check anyway...

                    pha
                    PushLong #theParam
                    PushLong #myCtlRect
                    _PtInRect
                    pla
                    beq NotInFrame      ; no in the frame. Signal noPart

; Now see if we differentiate between the frame and the interior

                    ldy #octlFlag
                    lda [<CtlPtr],y
                    and #dragIntMask
                    bne RetFramePart    ; nope - everything drags

; Yes we do. Now see if we hit the frame fair and square. Inset the
; control's bounding rectangle by the size of the knobs that are on
; it to create an inner rectangle. If we fall between those two
; rectangles, then I say that you have landed on the frame. If we
; got this far, then we know that we are within the outer rectangle.
; Now see if we are outside of the inner rectangle.

                    ldx #6              ; make a copy of the control rect
CopyLoop            lda myCtlRect,x     ; so that we can reduce it.
                    sta innerRect,x
                    dex
                    dex
                    bpl CopyLoop

                    PushLong #innerRect ; push on pointer to the rect to inset
                    PushWord FrameWidth ; push on the amounts to inset it by
                    PushWord FrameHeight ; (these vals inited by CalcCorners)
                    _InsetRect

                    pha                 ; see if we are outside of the inner
                    PushLong #theParam  ; rectangle
                    PushLong #innerRect
                    _PtInRect

                    pla                 ; check the result
                    beq RetFramePart    ; we were between rects!

NotInFrame          lda #noPart         ; Not *exactly* on the frame.
                    rts

innerRect           ds 8

                    end
