; Copyright 1998 Acorn Computers Ltd
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
; > Wimp.s.AutoScroll

;;-----------------------------------------------------------------------------
;; Automatic window scrolling
;;-----------------------------------------------------------------------------

      [ Autoscr

; Flags layout

af_enable       *       2_00000000000000000000000000000011
af_horizontal   *       2_00000000000000000000000000000001
af_vertical     *       2_00000000000000000000000000000010
af_scrollrq     *       2_00000000000000000000000000000100
af_read         *       2_00000000000000000000000010000000
af_scrolling    *       2_00000000000000000000000100000000
af_ptrout       *       2_00000000000000000000001000000000
af_ptrpause     *       2_00000000000000000000010000000000
af_ptrin        *       2_00000000000000000000100000000000
af_ptrleft      *       2_00000000000000000001000000000000
af_ptrdown      *       2_00000000000000000010000000000000
af_ptrright     *       2_00000000000000000100000000000000
af_ptrup        *       2_00000000000000001000000000000000
af_canleft      *       2_00000000000000010000000000000000
af_candown      *       2_00000000000000100000000000000000
af_canright     *       2_00000000000001000000000000000000
af_canup        *       2_00000000000010000000000000000000
af_status_bits  *       2_00000000000011111111111100000000
af_status_can   *       2_00000000000011110000000000000000
af_status_horiz *       2_00000000000001010000000000000000
af_status_vert  *       2_00000000000010100000000000000000

; Block definition
                ^       0
a_handle        #       4
a_pausex0       #       4
a_pausey0       #       4
a_pausex1       #       4
a_pausey1       #       4
a_pauselen      #       4
a_rout          #       4
a_wsptr         #       4

;;-----------------------------------------------------------------------------
;; SWI Wimp_AutoScroll
;;
;; in   R0 = autoscroll state flags
;;           bit 0 set => enable horizontal scrolling
;;           bit 1 set => enable vertical scrolling
;;           bit 2 set => send Scroll_Request instead of Open_Window_Request
;;           bit 7 set => just read current states of flags/block
;;      R1 -> block
;;           +0  window handle to scroll (must be owned by task)
;;           +4  left pause zone size
;;           +8  bottom pause zone size
;;           +12 right pause zone size
;;           +16 top pause zone size
;;           +20 pause duration (0 = pause not required, -1 = default length)
;;           +24 pointer-changing routine (0 for none, 1 for default)
;;           +28 workspace pointer for routine (if [R1,#24] >= &8000)
;; out  R0 = new autoscroll state flags
;;           bits 0-2 preserved, or (if bit 7 set on entry) read
;;           bit 7 clear
;;           bit 8  set => pause complete
;;           bit 9  set => pointer not over visible area rectangles
;;           bit 10 set => pointer over pause zone(s) rectangles
;;           bit 11 set => pointer over centre zone rectangles
;;           bit 12 set => pointer left of centre zone
;;           bit 13 set => pointer below centre zone
;;           bit 14 set => pointer right of centre zone
;;           bit 15 set => pointer above centre zone
;;           bit 16 set => there is work area left of the visible area
;;           bit 17 set => there is work area below the visible area
;;           bit 18 set => there is work area right of the visible area
;;           bit 19 set => there is work area above the visible area
;;-----------------------------------------------------------------------------

SWIWimp_AutoScroll
        MyEntry "AutoScroll"

        TST     R0, #af_read
        BNE     return_autoscroll_state ; Wimp_Init not necessary to read state

        LDR     R14, taskhandle
        LDR     R14, [wsptr, R14]
        TST     R14, #task_unused
        MyXError  WimpBadOp, NE, L
        BVS     ExitWimp                ; check alive

        TST     R0, #af_enable
        BEQ     %FT01                   ; don't bother validating if disabling

        CMP     userblk, #ApplicationStart
        MyXError  WimpBadPtrInR1, LO, L
        BVS     ExitWimp                ; then check pointer

        LDR     handle, [userblk, #a_handle]
        BL      checkhandle_owner
        BVS     ExitWimp                ; then check it's one of our windows

        ASSERT  a_handle = 0
        LDMIA   userblk, {R1, cx0, cy0, cx1, cy1, x0, y0, x1}
01
        BL      int_autoscroll

        LDRVC   R0, autoscr_state
        B       ExitWimp

return_autoscroll_state
        CMP     userblk, #ApplicationStart
        MyXError  WimpBadPtrInR1, LO, L
        BVS     ExitWimp                ; just check pointer

        ADRL    R14, autoscr_state
        LDMIA   R14, {R0, R1, cx0, cy0, cx1, cy1, x0, y0, x1}

        TST     R0, #af_enable
        ASSERT  a_handle = 0
        STMNEIA userblk, {R1, cx0, cy0, cx1, cy1, x0, y0, x1}

        B       ExitWimp

;;-----------------------------------------------------------------------------
;; int_autoscroll
;;
;; in   R0  = autoscroll state flags (see above)
;;      R1  = (external) window handle to scroll
;;      cx0 = left pause zone size
;;      cy0 = bottom pause zone size
;;      cx1 = right pause zone size
;;      cy1 = top pause zone size
;;      x0  = pause duration (0 = pause not required, -1 = default length)
;;      y0  = pointer routine (or 0 or 1)
;;      x1  = pointer routine workspace (if y0 >= &8000)
;;-----------------------------------------------------------------------------

int_autoscroll
        Entry   "R0-x1"
        MOV     R0, #0
        BL      update_autoscroll_state ; deactivate any existing autoscroll
                                        ; (does nothing if already off)
        LDR     R0, [sp]

        ADRL    R14, autoscr_handle
        STMIA   R14, {R1, cx0, cy0, cx1, cy1, x0, y0, x1}

        CMP     x0, #-1                 ; default pause time requested?
        LDREQB  x0, autoscr_default_pause
        MOVEQ   x0, x0, LSL #1
        ADDEQ   x0, x0, x0, LSL #2      ; load default time *10 (ds -> cs)

        CMP     y0, #ApplicationStart
        BHS     %FT01                   ; if a user routine, leave alone

        TST     y0, #1                  ; NB if you add further types, keeping
        MOVEQ   y0, #0                  ; bit 0 set will guarantee that some
        ADRNE   y0, autoscroll_pointer_routine_1 ; sort of pointer is displayed
        MOV     x1, wsptr               ; even when running on this old Wimp

01      ADRL    R14, autoscr_pause
        STMIA   R14, {x0, y0, x1}

        BL      update_autoscroll_state

        CLRV
        TST     R0, #af_enable
        EXIT    NE

        MOV     R0, #SpriteReason_SetPointerShape
        ADRL    R14, special_pointer    ; if turned off, get pointer to sprite
        LDR     R14, [R14]              ; name to return shape number 1 to
        TEQ     R14, #0
        ADREQL  R2, ptr_default2
        ADRNEL  R2, pointer_sprite
        MOV     R3, #&61                ; just reprogram the shape
        MOV     R4, #0                  ; assume origin at top-left
        MOV     R5, #0
        MOV     R6, #0
        MOV     R7, #0
        SWI     XWimp_SpriteOp
        CLRV
        EXIT

update_autoscroll_state
; In: R0 = new autoscroll state, in bits 0-4
; Sends a message to the autoscroll owner if the state (of any bit) has changed
        Entry   "R0-R2"
        LDR     R1, autoscr_state
        STR     R0, autoscr_state
        BL      update_autoscroll_flags
        LDR     R0, autoscr_state

        CLRV
        TEQ     R0, R1
        EXIT    EQ                      ; no change

        LDR     R14, autoscr_rout
        TEQ     R14, #0
        EXIT    EQ                      ; no pointer update routine

        CLRV                            ; make sure clean on entry
        Push    "wsptr"
        LDR     R14, autoscr_rout
        Push    "R14"                   ; store new PC
        LDR     R14, autoscr_wsptr
        Push    "R14"                   ; store new wsptr
        MOV     R14, PC                 ; gets PC+8
        Pull    "wsptr, PC"             ; BL [autoscr_rout]
        Pull    "wsptr"                 ; retrieve Wimp wsptr
        EXIT

update_autoscroll_flags
; Updates status flag bits to reflect current state
        Entry   "R0-y1,handle,userblk"
        ADRL    R14, autoscr_state
        LDMIA   R14, {R0, R1, cx0, cy0, cx1, cy1}

        TST     R0, #af_enable
        MOVEQ   R0, #0                  ; if turned off, zero all bits and exit
        STREQ   R0, autoscr_state
        STREQB  R0, autoscr_scrolling
        STREQB  R0, autoscr_pausing
        EXIT    EQ

        LDR     R14, =af_status_bits    ; all bits default to 0
        BIC     userblk, R0, R14        ; keep in userblk until end of routine

        Abs     handle, R1
        LDR     R0, mousexpos
        LDR     R1, mouseypos

        ADD     R14, handle, #w_wax0
        LDMIA   R14, {cx0, cy0, cx1, cy1} ; cx0-cy1 = visible area
        SUB     cx1, cx1, #1              ; make inclusive
        SUB     cy1, cy1, #1

        BL      get_centre_zone         ; x0-y1 = centre zone
        SUB     x1, x1, #1              ; make inclusive
        SUB     y1, y1, #1

        CMP     cx0, R0                 ; is pointer outside visible area?
        CMPLE   R0, cx1
        CMPLE   cy0, R1
        CMPLE   R1, cy1
        ORRGT   userblk, userblk, #af_ptrout
        BGT     %FT01

        CMP     x0, R0                  ; is it in the centre, or a pause zone?
        CMPLE   R0, x1
        CMPLE   y0, R1
        CMPLE   R1, y1
        ORRGT   userblk, userblk, #af_ptrpause
        ORRLE   userblk, userblk, #af_ptrin
        MOVLE   R14, #0
        STRLEB  R14, autoscr_scrolling  ; deactivate scrolling if in centre
        STRLEB  R14, autoscr_pausing
01
        LDRB    R14, autoscr_scrolling  ; read separate flag for this
        TEQ     R14, #0
        ORRNE   userblk, userblk, #af_scrolling

        CMP     R0, x0                  ; is it in the left pause zone?
        ORRLT   userblk, userblk, #af_ptrleft

        CMP     R1, y0                  ; is it in the down pause zone?
        ORRLT   userblk, userblk, #af_ptrdown

        CMP     R0, x1                  ; is it in the right pause zone?
        ORRGT   userblk, userblk, #af_ptrright

        CMP     R1, y1                  ; is it in the up pause zone?
        ORRGT   userblk, userblk, #af_ptrup

        ADD     R14, handle, #w_wax0    ; get visible area and scroll offsets
        LDMIA   R14, {cx0, cy0, cx1, cy1, x0, y1}
        SUB     cx1, cx1, cx0           ; convert to work area coordinates
        SUB     cy0, cy0, cy1
        MOV     cx0, x0
        MOV     cy1, y1
        ADD     cx1, cx1, cx0
        ADD     cy0, cy0, cy1

        ADD     R14, handle, #w_wex0    ; get work area extent
        LDMIA   R14, {x0, y0, x1, y1}

        CMP     cx0, x0                 ; is there space to move left?
        ORRGT   userblk, userblk, #af_canleft

        CMP     cy0, y0                 ; is there space to move down?
        ORRGT   userblk, userblk, #af_candown

        CMP     cx1, x1                 ; is there space to move right?
        ORRLT   userblk, userblk, #af_canright

        CMP     cy1, y1                 ; is there space to move up?
        ORRLT   userblk, userblk, #af_canup

        AND     R0, userblk, #af_enable
        TEQ     R0, #af_vertical        ; do we clear the horizontal bits?
        BICEQ   userblk, userblk, #af_status_horiz

        TEQ     R0, #af_horizontal      ; do we clear the vertical bits?
        BICEQ   userblk, userblk, #af_status_vert

        STR     userblk, autoscr_state
        EXIT

get_centre_zone
; In:  handle = autoscroll window handle
; Out: x0-y0  = centre zone (may be zero width or height)
        Entry   "cx0-cy1"
        ADRL    R14, autoscr_pz_x0      ; load zone sizes
        LDMIA   R14, {cx0, cy0, cx1, cy1}

        CMP     cx0, #0                 ; can't be negative
        MOVLT   cx0, #0
        CMP     cy0, #0
        MOVLT   cy0, #0
        CMP     cx1, #0
        MOVLT   cx1, #0
        CMP     cy1, #0
        MOVLT   cy1, #0

        LDR     R14, autoscr_state
        AND     R14, R14, #af_enable
        TEQ     R14, #af_vertical       ; if just vertical scrolling,
        MOVEQ   cx0, #0                 ; don't supply horizontal pause zones
        MOVEQ   cx1, #0
        TEQ     R14, #af_horizontal     ; if just horizontal scrolling,
        MOVEQ   cy0, #0                 ; don't supply vertical pause zones
        MOVEQ   cy1, #0

        ADD     R14, handle, #w_wax0    ; load visible area
        LDMIA   R14, {x0, y0, x1, y1}

        ADD     x0, x0, cx0             ; add on the specified borders
        SUB     x1, x1, cx1
        ADD     y0, y0, cy0
        SUB     y1, y1, cy1

        CMP     x1, x0                  ; if x pause zone won't fit
        ADDLT   x1, x1, x0
        MOVLT   x0, x1, LSR#1           ; find the middle of the overlap
        MOVLT   x1, x0

        CMP     y1, y0                  ; if y pause zone won't fit
        ADDLT   y1, y1, y0
        MOVLT   y0, y1, LSR#1           ; find the middle of the overlap
        MOVLT   y1, y0

        EXIT

autoscroll_pointer_routine_1
; handles pointer changes according to the default rules
; In: R0 = new autoscroll state
;     R1 = old autoscroll state
        Entry   "R0-R7"
        TST     R0, #af_scrolling       ; don't show pointer if not scrolling
        MOVEQ   R0, #0
        TST     R1, #af_scrolling
        MOVEQ   R1, #0

        AND     R0, R0, R0, LSL #4      ; only consider directions in which we
        AND     R0, R0, #af_status_can  ; can scroll *and* where the pointer is
        AND     R1, R1, R1, LSL #4      ; suitably positioned
        AND     R1, R1, #af_status_can

        TEQ     R0, #0                  ; reduce to on/off status
        MOVNE   R0, #1
        TEQ     R1, #0
        MOVNE   R1, #1

        TEQ     R0, R1                  ; don't do anything if status constant
        EXIT    EQ

        TEQ     R0, #0                  ; is it being turned on or off?
        BEQ     %FT10

        ; turn *on* autoscroll pointer

        ADRL    R4, autoscr_old_ptr_colours

        MOV     R0, #1                  ; remember old colour 1
        MOV     R1, #&19
        SWI     XOS_ReadPalette
        BIC     R2, R2, #&FF
        STR     R2, [R4], #4

        MOV     R0, #2                  ; remember old colour 2
        MOV     R1, #&19
        SWI     XOS_ReadPalette
        BIC     R2, R2, #&FF
        STR     R2, [R4], #4

        MOV     R0, #3                  ; remember old colour 3
        MOV     R1, #&19
        SWI     XOS_ReadPalette
        BIC     R2, R2, #&FF
        STR     R2, [R4], #4

        ASSERT  autoscr_old_ptr_number - autoscr_old_ptr_colours = 3*4
        MOV     R0,#&6A                 ; set/read pointer shape no
        MOV     R1,#127                 ; this is invalid, so won't be set
        SWI     XOS_Byte                ; R1 := actual shape number
        AND     R1, R1, #&F             ; forget linkage flag
        STRB    R1, [R4]                ; remember old pointer number

        ADR     R0, autoscroll_pointers
        LDR     R2, [sp]                ; get R0 from stack
        AND     R2, R2, #af_enable      ; get allowed directions
        SUB     R2, R2, #1
        ADD     R2, R0, R2, LSL#4       ; point at the details for relevant ptr

        MOV     R0, #SpriteReason_ReadSpriteSize
        SWI     XWimp_SpriteOp
        MOV     R5, R4, LSR#1           ; active point y-offset
        MOV     R4, R3, LSR#1           ; active point x-offset

        TEQ     R1, #1                  ; does pointer number need changing?
        MOVEQ   R3, #&61                ; just change shape
        MOVNE   R3, #&21                ; change number and shape
                                        ; bit 5 clear => uses Wimp palette
        MOV     R6, #0                  ; scale for mode
        MOV     R7, #0                  ; no translation table
        MOV     R0, #SpriteReason_SetPointerShape
        SWI     XWimp_SpriteOp

        CLRV
        EXIT

10      ; turn *off* autoscroll pointer
        LDRB    R3, autoscr_old_ptr_number
        TEQ     R3, #1

        MOVNE   R0, #SpriteReason_SetPointerShape
        MOVNE   R2, #0
        ORRNE   R3, R3, #&30            ; just change number
        MOVNE   R4, #0
        MOVNE   R5, #0
        MOVNE   R6, #0
        MOVNE   R7, #0
        SWINE   XWimp_SpriteOp

        MOV     R0, #SpriteReason_SetPointerShape
        ADRL    R2, ptr_default2
        MOV     R3, #&61                ; just change shape 1 to ptr_default
        MOV     R4, #0
        MOV     R5, #0
        MOV     R6, #0
        MOV     R7, #0
        SWI     XWimp_SpriteOp

        ADRL    R2, autoscr_old_ptr_colours

        LDR     R0, [R2], #4            ; R0 = &BBGGRR00
        MOV     R1, R0, LSR#24          ; R1 = &BB
        MOV     R0, R0, LSL#8           ; R0 = &GGRR0000
        LDR     R3, =&1901
        ORR     R0, R0, R3
        Push    "R0, R1"
        MOV     R0, #OsWord_WritePalette
        MOV     R1, sp
        SWI     XOS_Word                ; restore original pointer colour 1
        ADD     sp, sp, #8

        LDR     R0, [R2], #4            ; R0 = &BBGGRR00
        MOV     R1, R0, LSR#24          ; R1 = &BB
        MOV     R0, R0, LSL#8           ; R0 = &GGRR0000
        LDR     R3, =&1902
        ORR     R0, R0, R3
        Push    "R0, R1"
        MOV     R0, #OsWord_WritePalette
        MOV     R1, sp
        SWI     XOS_Word                ; restore original pointer colour 2
        ADD     sp, sp, #8

        LDR     R0, [R2], #4            ; R0 = &BBGGRR00
        MOV     R1, R0, LSR#24          ; R1 = &BB
        MOV     R0, R0, LSL#8           ; R0 = &GGRR0000
        LDR     R3, =&1903
        ORR     R0, R0, R3
        Push    "R0, R1"
        MOV     R0, #OsWord_WritePalette
        MOV     R1, sp
        SWI     XOS_Word                ; restore original pointer colour 3
        ADD     sp, sp, #8

        CLRV
        EXIT

autoscroll_pointers
        DCB     "ptr_autoscrh", 0, 0, 0, 0
        DCB     "ptr_autoscrv", 0, 0, 0, 0
        DCB     "ptr_autoscr", 0, 0, 0, 0, 0

poll_autoscroll
; Called during poll - update scroll flags, issue open/scroll rqs if necessary
        EntryS  "R0-cy1"
        LDR     R0, autoscr_state
        BL      update_autoscroll_state

        LDRB    R0, autoscr_scrolling   ; what we do depends upon current state
        TEQ     R0, #0
        BNE     continueautoscroll
        LDRB    R0, autoscr_pausing
        TEQ     R0, #0
        BNE     %FT10

        ; Neither pausing nor scrolling
        LDR     R0, autoscr_state
        TST     R0, #af_ptrin
        EXITS   NE                      ; nothing to do in centre

        LDR     R1, autoscr_pause
        TEQ     R1, #0                  ; if no pause, start scrolling
        BEQ     startautoscroll         ; as soon as we leave the centre

        TST     R0, #af_ptrout
        EXITS   NE                      ; can't do anything if outside

        SWI     XOS_ReadMonotonicTime   ; R0 = current time
        ADD     R0, R0, R1              ; R0 = time when pause ends
        STR     R0, autoscr_next_t
        LDR     R0, mousexpos           ; recalculate pause end time
        STR     R0, autoscr_last_x      ; if these change subsequently
        LDR     R0, mouseypos           ;
        STR     R0, autoscr_last_y      ;
        MOV     R0, #-1
        STRB    R0, autoscr_pausing     ; mark pausing
        EXITS

10      ; Pausing
        LDR     R0, autoscr_state
        TST     R0, #af_ptrpause
        MOVEQ   R0, #0                  ; abandon the pause if no longer in
        STREQB  R0, autoscr_pausing     ; the pause zone(s)
        EXITS   EQ

        SWI     XOS_ReadMonotonicTime   ; R0 = current time
        LDR     cx0, mousexpos
        LDR     cx1, autoscr_last_x
        TEQ     cx0, cx1
        LDREQ   cy0, mouseypos
        LDREQ   cy1, autoscr_last_y
        TEQEQ   cy0, cy1

        LDRNE   R1, autoscr_pause       ; if the pointer's moved, update the
        ADDNE   R0, R0, R1              ; pointer position and pause end time
        STRNE   R0, autoscr_next_t
        STRNE   cx0, autoscr_last_x
        STRNE   cy0, autoscr_last_y
        EXITS   NE

        LDR     R1, autoscr_next_t      ; exit if pause hasn't finished
        CMP     R0, R1
        EXITS   LO                      ; else drop through to startautoscroll

startautoscroll
        MOV     R0, #-1                 ; set flags as necessary
        STRB    R0, autoscr_scrolling
        MOV     R0, #0
        STRB    R0, autoscr_pausing
        LDR     R0, autoscr_state
        BL      update_autoscroll_state

        SWI     XOS_ReadMonotonicTime   ; R0 = current time
        STR     R0, autoscr_last_t
        ADD     R0, R0, #autoscr_update_delay
        STR     R0, autoscr_next_t

        LDR     R0, autoscr_state
        TST     R0, #af_scrollrq        ; if issuing Open_Window_Requests,
        EXITS   EQ                      ; don't do anything else at this time

        LDR     handle, autoscr_handle
        Abs     handle, handle
        LDR     R0, [handle, #w_taskhandle]
        Task    "R0",, "Autoscroll start"
        ADD     sp, sp, #Proc_RegOffset+7*4     ; skip stuff pushed to stack
        MOV     R1, #0                          ; x scroll "direction"
        MOV     R2, #0                          ; y scroll "direction"
        B       userscroll                      ; return Scroll_Request event

continueautoscroll
        LDRB    R0, autoscr_pausing     ; check flag - ensure control passes
        TEQ     R0, #0                  ; further down the Wimp_Poll action
        MOVNE   R0, #0                  ; list at least once per window update
        STRB    R0, autoscr_pausing
        EXITS   NE

        SWI     XOS_ReadMonotonicTime   ; R0 = current time
        LDR     R1, autoscr_next_t
        CMP     R1, R0
        EXITS   HI                      ; give other events a chance

        MOV     R1, #-1
        STRB    R1, autoscr_pausing     ; give other events a chance next time

        LDR     R1, autoscr_last_t
        SUB     R1, R0, R1              ; time since last update
        STR     R1, tempworkspace       ; keep in case we need it later
        STR     R0, autoscr_last_t
        ADD     R0, R0, #autoscr_update_delay
        STR     R0, autoscr_next_t

        LDR     handle, autoscr_handle
        Abs     handle, handle
        LDR     R0, [handle, #w_taskhandle]
        Task    "R0",, "Autoscroll continue"

        LDR     R0, autoscr_state
        TST     R0, #af_scrollrq
        AND     R0, R0, R0, LSL#4       ; clear "can" bits where pointer isn't
        BEQ     continueautoscroll_open

        ADD     sp, sp, #Proc_RegOffset+7*4     ; skip stuff pushed to stack
        MOV     R1, #0                  ; determine scroll "directions"
        MOV     R2, #0
        TST     R0, #af_canleft
        MOVNE   R1, #-3
        TST     R0, #af_candown
        MOVNE   R2, #-3
        TST     R0, #af_canright
        MOVNE   R1, #+3
        TST     R0, #af_canup
        MOVNE   R2, #+3
        B       userscroll              ; return Scroll_Request event

continueautoscroll_open
        TST     R0, #af_status_can
        EXITS   EQ                      ; don't do anything if we can't scroll

        ADD     sp, sp, #Proc_RegOffset+7*4     ; skip stuff pushed to stack
        ADD     R14, handle, #w_wax0
        LDMIA   R14, {cx0, cy0, cx1, cy1, x0, y0}
        STMIA   userblk, {R0, cx0, cy0, cx1, cy1}

        MOV     cx0, x0                 ; old scroll offsets
        MOV     cy0, y0
        BL      get_centre_zone         ; overwrites x0-y1
        LDR     R14, dx
        SUB     x1, x1, R14             ; make inclusive
        LDR     R14, dy
        SUB     y1, y1, R14

        LDR     R14, tempworkspace      ; retrieve time since last update
        LDR     R1, mousexpos           ; find pointer x offset from window
        TST     R0, #af_canleft
        SUBNE   R1, R1, x0
        SUBEQ   R1, R1, x1
        TST     R0, #af_status_horiz
        MULNE   R1, R14, R1             ; multiply by time, and scale down
        MOVNE   R1, R1, ASR#autoscr_speed_factor
        TEQ     R1, #0
        LDRPL   R14, dx                 ; compensate for the asymmetric effect
        ADDPL   R1, R1, R14             ; of the rounding-down in OpenWindow
        TST     R0, #af_status_horiz
        ADDNE   cx0, cx0, R1            ; add to old scroll offset

        LDR     R14, tempworkspace      ; retrieve time since last update
        LDR     R1, mouseypos           ; find pointer y offset from window
        TST     R0, #af_candown
        SUBNE   R1, R1, y0
        SUBEQ   R1, R1, y1
        TST     R0, #af_status_vert
        MULNE   R1, R14, R1             ; multiply by time, and scale down
        MOVNE   R1, R1, ASR#autoscr_speed_factor
        TEQ     R1, #0
        LDRPL   R14, dy                 ; compensate for the asymmetric effect
        ADDPL   R1, R1, R14             ; of the rounding-down in OpenWindow
        TST     R0, #af_status_vert
        ADDNE   cy0, cy0, R1            ; add to old scroll offset

        Rel     cx1, handle             ; finish writing to [userblk]
        STR     cx1, [userblk, #u_handle]
        ADD     R14, userblk, #u_scx
        STMIA   R14, {cx0, cy0, cx1}    ; store scx, scy, bhandle

        B       Exit_OpenWindow         ; finally, get the window re-opened!

      ]
        LTORG
        DCD     12648430                ; amuse the hackers... ;-)
        END