; Copyright 2019 RISC OS Open 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.Clipboard

 [ CnP
;  Clipboard
;  Implement writable icons with clipboard support

; sprite buffer area for redraws
; we buffer the redraw in a sprite, then write this to the screen
; it will hopefully reduce flicker

; get current sprite area
; if none defined, then create one based on the current mode
; need to make sure it's big enough for the largest writable (may need to scale it up)
; keep the pool in a DA as may fragment if keeps increasing size...?

; area size is width x height x BPP + header

; area data format
                                ^   0
clipboard_area_status           #   4   ; 0 if not set up, 1 if OK to go
clipboard_mode_word             #   4   ; mode word for creating sprites in current screen mode

clipboard_save_area_offset      #   4   ; offset in workspace of the VDU save area
clipboard_save_area_size        #   4   ; size needed for VDU vars save when switching to sprite output
clipboard_prev_status           # 4*4   ; previous save status (R0-R3)

clipboard_selection_font_ctrl   #   0   ; block for font control sequences
        ; we need enough space for two sequences:
        ; * start of highlight and end of highlight
clipboard_font_ctrl_start_index #   4   ; byte index into string of highlight start
clipboard_font_ctrl_start_len   #   4   ; length of the control sequence (should be 8 bytes)
clipboard_font_ctrl_start_codes #   8   ; byte sequence 19,bg_r,bg_g,bg_b,fg_r,fg_g,fg_b,offset
clipboard_font_ctrl_end_index   #   4   ; byte index into string of highlight end
clipboard_font_ctrl_end_len     #   4   ; length of the control sequence (should be 8 bytes)
clipboard_font_ctrl_end_codes   #   8   ; byte sequence 19,bg_r,bg_g,bg_b,fg_r,fg_g,fg_b,offset
clipboard_font_ctrl_terminator  #   4   ; terminator should be -1

cnp_message_dataload_park       # 256   ; holding pen for Message_DataLoads that we may need to intercept

; the task workspace bits follow
; variables.  These are basically as per the drag and drop protocol
; Drag receiver
cbtask_var_claiming             #   1
cbtask_var_pointer_altered      #   1
cbtask_var_pausing              #   1
cbtask_var_ghostcaret           #   1
cbtask_var_last_x               #   4
cbtask_var_claimed_windowhandle #   4
cbtask_var_claimed_iconhandle   #   4
cbtask_var_last_scrollx         #   4

; Drag sender
cbtask_var_dragging             #   1
cbtask_var_drag_finished        #   1
cbtask_var_drag_aborted         #   1
cbtask_var_shift_pressed        #   1
cbtask_var_delete_source        #   1
cbtask_var_padding              #   3
cbtask_var_claimant             #   4
cbtask_var_lastref              #   4
cbtask_var_old_dragclaim_flags  #   4
cbtask_var_dragbox_parent       #   16
cbtask_var_dragbox_box_os       #   16
cbtask_var_dragbox_box_mpt      #   16
cbtask_var_source_window        #   4
cbtask_var_source_icon          #   4
cbtask_var_source_low_bound     #   4   ; original selection low bound
cbtask_var_source_high_bound    #   4   ; original selection high bound

; task workspace
cbtask_pollblock                # 256   ; Wimp_Poll data block
cbtask_stack                    # 256
cbtask_stacklimit               #   0

; flexible storage area fills rest of the DA workspace
clipboard_flex_base             #   4   ; base of the clipboard heap

clipboard_da_minsize            #   0   ; minimum DA size

                                ^   0
clipboard_flexblock_sprite      #   1   ; sprite area
clipboard_flexblock_savearea    #   1   ; VDU save area
clipboard_flexblock_clipdata    #   1   ; clipboard data
clipboard_flexblock_pastedata   #   1   ; data that's come from outside to be pasted
clipboard_flexblock_validation  #   1   ; validation string (if any)
clipboard_flexblock_icontext    #   1   ; imported icon text from another task
clipboard_flexblock_dragdata    #   1   ; text from an icon that is being dragged
clipboard_flexblock_total       #   0   ; total number of blocks we're using.  The block order is significant.

; amount of data to allocate to the area
; this amount gets added to the sprite size to give the area size
clipboard_sprite_area_spritepad *  SpriteAreaCBsize + SpriteCBsize

; colour definitions
colour_white                     * &FFFFFF00
colour_shaded                    * &80808000
colour_ghostcaret                * &80808000

clipboard_draw_selection_block_vdu5
; draw the filled area that goes behind the selection for a vdu5 icon
; r10 is icon block
        Push    "R0-R9,R14"

        LDR     R8,selectionwindowaddr
        LDR     R6,[R8,#w_seldata+wsellowindex]
        LDR     R5,[R8,#w_seldata+wselhighindex]
        CMP     R5,R6
        Pull    "R0-R9,PC",EQ           ; no selection block, really - zero width

        ; check colour - faded or not?
        LDR     R7,selectionwindow
        Abs     R7,R7
        LDR     R8,selectionwindowaddr
        EORS    R8,R8,R7                ; if r8=0 then it's the selection window
        BEQ     %FT01

        ; shaded selection
        LDR     R0,=colour_shaded
        MOV     R3,#0
        MOV     R4,#0
        SWI     XColourTrans_SetGCOL
01
        ; cursor is currently at start of the text paint zone

        ; get icon height
        LDR     R0,[R10,#i_bby0]
        LDR     R1,[R10,#i_bby1]
        SUB     R7,R1,R0
        SUB     R7,R7,#6

        LDR     R9,writeabledir
        TEQ     R9,#0

        ; get current cursor posn
        MOV     R0,#138
        MOV     R1,#139
        MOV     R2,#-1
        Push    "R0-R2"
        MOV     R0,sp
        MOV     R1,sp
        SWI     XOS_ReadVduVariables

        ; move to start of selection
        MOV     R0,#0                   ; move relative
        MOV     R1,R5,LSL #4
        MVNNE   R1,R1                   ; reverse direction if we're doing RTL printing
        ADDNE   R1,R1,#16
        MVN     R4,R1                   ; store offset for later
        MOV     R2,R7,LSR #1
        SUB     R2,R2,#16
        SWI     XOS_Plot

        ; plot rectangle to end of selection
        MOV     R0,#97                  ; plot rectangle relative
        SUB     R1,R6,R5                ; width of selection block
        MOV     R1,R1,LSL #4
        TEQ     R9,#0
        MVNNE   R1,R1
        SUB     R4,R4,R1                ; store offset for later
        MVN     R2,R7                   ; height of the rectangle
        ADD     R2,R2,#4
        SWI     XOS_Plot
        ; reset cursor position as we leave
        MOV     R0,#4
        Pull    "R1,R2,R3"              ; get stored vars off stack; r3 is the list terminator so ignore
        SWI     XOS_Plot

        TEQ     R8,#0                   ; do we need to reset colours?
        Pull    "R0-R9,PC",EQ           ; no - leave

        ; reset to previous colour settings
        LDR     R0,truefgcolour
        MOV     R3,#0
        MOV     R4,#0
        SWI     XColourTrans_SetGCOL
        Pull    "R0-R9,PC"

clipboard_draw_selection_block_font
; draw the filled area that goes behind the selection
; r1 points to string
; r10 is icon block
        Push    "R0-R9,R14"

        LDRB    R9,cnp_buffered
        TEQ     R9,#1
        Pull    "R0-R9,PC",NE

        LDR     R8,selectionwindowaddr
        LDR     R6,[R8,#w_seldata+wsellowindex]
        LDR     R5,[R8,#w_seldata+wselhighindex]
        CMP     R5,R6
        Pull    "R0-R9,PC",EQ           ; no selection block, really - zero width

        ; get base X coord of the string to be painted from stack
        ADD     R9,sp,#11*4+cx1*4
        ADD     R9,R9,R7
        LDR     R6,[R9]

        ; find out where the first edge of the selection block should be
        MOV     R0,#0
        MOV     R2,#1 :SHL: 7           ; r7 is string length
        MOV     R3,#bignum
        MOV     R4,#bignum
        LDR     R7,[R8,#w_seldata+wsellowindex]            ; start index of selection
        ADD     R7,R7,#2                ; take into account the font selection bytes
        SWI     XFont_ScanString
        ADDVS   SP,SP,#4
        Pull    "R1-R9,PC",VS

        ; we want OS units, not millipoints :(
        DivRem  R0,R3,#400,R2,norem

        LDR     R14,writeabledir
        TEQ     R14,#0
        RSBNE   R0,R0,#12               ; we end up in the wrong place else?

        ; R0 now has the X coord to start the selection.  Want offset from R6 (original paint coord)
        LDR     R6,[R9]
        ADD     R1,R0,R6

        Plot    4,R1,#4                 ; move to (r3,#4)

        ; get offset to end of selection block
        MOV     R0,#0
        LDR     R1,[sp,#4]              ; was changed by last call to scanstring
        MOV     R2,#1 :SHL: 7           ; r7 is string length
        MOV     R3,#bignum
        MOV     R4,#bignum
        LDR     R7,[R8,#w_seldata+wselhighindex]            ; end index of selection
        ADD     R7,R7,#10               ; take into account the font selection bytes and colour change for start selection
        SWI     XFont_ScanString
        ADDVS   SP,SP,#4
        Pull    "R1-R9,PC",VS

        ; convert to OS
        DivRem  R0,R3,#400,R2,norem
        LDR     R14,writeabledir
        TEQ     R14,#0
        RSBNE   R0,R0,#12

        LDR     R6,[R9]
        ADD     R1,R0,R6

        ; work out height of selection block (icon height -8); we start at +4
        LDR     R4,[R10,#i_bby0]
        LDR     R5,[R10,#i_bby1]
        SUB     R2,R5,R4
        SUB     R2,R2,#6

        ; check colour - faded or not?
        LDR     R14,selectionwindow
        Abs     R14,R14
        LDR     R0,selectionwindowaddr
        CMP     R0,R14
        BEQ     %FT01

        ; we need to sort out a faded colour setting
        Push    "R1,R2"
        LDR     R0,=colour_shaded
        MOV     R3,#0
        MOV     R4,#0
        SWI     XColourTrans_SetGCOL
        Pull    "R1,R2"
        Plot    101,R1,R2

        ; reset to previous colour settings
        LDR     R0,truefgcolour
        MOV     R3,#0
        MOV     R4,#0
        SWI     XColourTrans_SetGCOL
        Pull    "R0-R9,PC"
01
        ; continue with simple colour settings
        Plot    101,R1,R2               ; rectangle to (R1,R2)

        Pull    "R0-R9,PC"

clipboard_set_font_colour_codes
; set up colour codes for a selection font
; exit, r7=pointer to the selection codes
        Push    "R0-R4,R14"

        LDRB    R14,cnp_buffered
        CMP     R14,#1
        MOVNE   R7,#nullptr
        Pull    "R0-R4,PC",NE

        LDR     R4,clipboard_spritearea_addr
        CMP     R4,#0
        MOVEQ   R7,#nullptr
        Pull    "R0-R4,PC",EQ           ; no area available

        ; get start and end index of the selection
        LDR     R3,selectionwindowaddr  ; window block has selection info
        LDR     R14,[R3,#w_seldata+wsellowindex]            ; start index of selection
        LDR     R0,[R3,#w_seldata+wselhighindex]                ; end index
        CMP     R0,R14
        MOVEQ   R7,#nullptr
        Pull    "R0-R4,PC",EQ           ; no block to draw

        STR     R14,[R4,#clipboard_font_ctrl_start_index]
        STR     R0,[R4,#clipboard_font_ctrl_end_index]

        MOV     R14,#8
        STR     R14,[R4,#clipboard_font_ctrl_start_len] ; code sequence length
        STR     R14,[R4,#clipboard_font_ctrl_end_len]   ; ditto

        ; new foreground colour.  If we are the active (unshaded) selection, use the background, else use &80808000
        ; stored as BBGGRR00 by the looks of things
        ; see if it's an active selection

        LDR     R14,selectionwindow
        Abs     R14,R14
        LDR     R0,selectionwindowaddr
        CMP     R0,R14

        LDREQ   R0,truefgcolour         ; we are flipping foreground/background
        LDREQ   R1,truebgcolour
        LDRNE   R1,=colour_white        ; shaded, so use white FG
        LDRNE   R0,=colour_shaded       ; and mid grey BG

        ; r0 has desired fg, r1 has desired bg for the selection
        ORR     R0,R0,#19               ; code to change font colour
        STR     R0,[R4,#clipboard_font_ctrl_start_codes]

        MOV     R1,R1,LSR #8
        ORR     R1,R1,#&0e000000        ; set offset colour code
        STR     R1,[R4,#clipboard_font_ctrl_start_codes+4]

        ; at end of selection, reset to existing colours in similar fashion
        LDR     R0,truebgcolour
        ORR     R0,R0,#19
        STR     R0,[R4,#clipboard_font_ctrl_end_codes]

        LDR     R1,truefgcolour
        MOV     R1,R1,LSR #8
        ORR     R1,R1,#&0e000000
        STR     R1,[R4,#clipboard_font_ctrl_end_codes+4]
        MOV     R14,#-1
        STR     R14,[R4,#clipboard_font_ctrl_terminator]

        ADD     R7,R4,#clipboard_selection_font_ctrl

        Pull    "R0-R4,PC"

        LTORG

clipboard_sprite_name
        = "0",0       ; only used internally in a private area, so short and sweet
clipboard_area_token
        = "WCS:Clipboard Workspace",0 ; messagetrans token
        ALIGN

; initial allocation settings
; when we first set up the area, we use this for a basic sprite as it should cover a number of use cases
; anything larger will be requested subsequently
clipboard_sprite_initial_x    * 256
clipboard_sprite_initial_y    * 26

clipboard_create_sprite_area
        ; create dynamic area for the writable icon pixmap store
        ; In: Nothing
        ; Out: V and R0 set if error; R0=0 otherwise
        ;      Rest preserved
        Push    "R0-R8,R14"

      [ UseDynamicArea
        ADRL    R0,message_block
        ADR     R1,clipboard_area_token
        MOV     R2,#0
        SWI     XMessageTrans_Lookup
        MOVVC   R8,R2
        MOVVC   R0,#DAReason_Create
        MOVVC   R1,#-1
        MOVVC   R2,#clipboard_da_minsize
        MOVVC   R3,#-1
        MOVVC   R4,#128
        MOVVC   R5,#8*1024*1024         ; allow up to 8MB size
        MOVVC   R6,#0
        MOVVC   R7,#-1
        SWIVC   XOS_DynamicArea
      |
        ; use RMA instead
        ; we need enough space for the base workspace and a list of areas
        ; area list is (ptr,length) array, so 8 bytes per area
        MOV     R0,#ModHandReason_Claim
        MOV     R3,#clipboard_da_minsize+8*clipboard_flexblock_total
        SWI     XOS_Module
        MOVVC   R1,#-1
        MOVVC   R3,R2
      ]
        ; no hope
        ADDVS   SP,SP,#4
        MOVVS   R14,#0
        STRVS   R14,clipboard_spritearea_addr
        Pull    "R1-R8,PC",VS           ; failed to create dynamic area or claim RMA - a bad thing

        ; store details for use
        STR     R1,clipboard_spritearea_num  ; in R12 store
        STR     R3,clipboard_spritearea_addr ; in R12 store

        ; initialise area with any key values
        MOV     R0,#0
        STR     R0,[R3,#clipboard_flex_base] ; heap is empty to start with
        STR     R0,[R3,#cnp_message_dataload_park] ; no stored messages

        ; create empty flex areas for sprite, save area, clipboard data, paste data etc
        MOV     R8,#clipboard_flexblock_total
01
        BL      clipboard_flex_create_block
        SUBS    R8,R8,#1
        BNE     %BT01
        Pull    "R0-R8,PC"

clipboard_calc_spritearea_size
; determine required size to hold the sprite area
; In:  R4,R5 - width, height of sprite in pixels
; Out: R1 - sprite area size needed
        Push    "R0,R2-R3,R14"
        ; get mode bpp
        LDR     R2,log2bpp              ; wimp holds this
        ; adjust cols to a round number
        ADD     R14,R4,#8
        BIC     R14,R14,#7

        ; calculate desired allocation
        MUL     R0,R14,R5
        ADD     R0,R0,R14
        SUBS    R2,R2,#3                ; adjust log2bpp
        MOVMI   R2,#0
        MOV     R1,R0,LSL R2            ; sprite size needed
        ADD     R1,R1,#clipboard_sprite_area_spritepad ; actual sprite area size needed (with sprite/file headers)
        Pull    "R0,R2-R3,PC"

clipboard_mode_changed
        ; reinitialise sprite area after a mode change (or init after start up)
        ; start with a basic initial sprite size
        ; In: Nothing
        ; Out: V and R0 set on error, otherwise all preserved
        Push    "R0-R9,R14"
        LDR     R8,clipboard_spritearea_addr
        CMP     R8,#0
        Pull    "R0-R9,PC",EQ           ; no area available

clipboard_mode_changed_int
        MOV     R4,#clipboard_sprite_initial_x
        MOV     R5,#clipboard_sprite_initial_y
        BL      clipboard_calc_spritearea_size
        MOV     R0,#clipboard_flexblock_sprite
        BL      clipboard_flex_set_block_size
        BVS     %FT60                     ; failed to set area size

        BL      clipboard_sprite_area_init

        MOV     R0,#clipboard_flexblock_sprite
        BL      clipboard_flex_get_block_addr
        MOV     R9,R0

        ; create initial sprite
        ; base on current screen mode by grabbing chunk of screen
        ; we can then use the generated mode word in future
        LDR     R0,=SpriteReason_GetSpriteUserCoords+256 ; user sprite area
        MOV     R1,R9
        ADRL    R2,clipboard_sprite_name
        MOV     R3,#0
        ; r4, r5 have the x,y size in pixels; adjust to screen coords for the grab:
        LDR     R14,log2py
        SUB     R5,R5,#1
        MOV     R7,R5,LSL R14
        LDR     R14,log2px
        SUB     R4,R4,#1
        MOV     R6,R4,LSL R14
        MOV     R5,#0
        MOV     R4,#0
        SWI     XOS_SpriteOp
        BVS     %FT60

        ; get the mode word for this one
        LDR     R0,=SpriteReason_ReadSpriteSize+512
        ADD     R2,R9,#16
        SWI     XOS_SpriteOp
        BVS     %FT60

        ; mode word is in R6
        STR     R6,[R8,#clipboard_mode_word]

        BL      clipboard_setup_save_area

        MOVVC   R14,#1
60
; finished
        MOVVS   R14,#0
        STR     R14,[R8,#clipboard_area_status]
        STRVS   R0,[sp]
        Pull    "R0-R9,PC"

clipboard_setup_save_area
        ; ensure enough space for a VDU save area and init the offsets
        ; entry: r8=pointer to DA base
        ;        r9=pointer to sprite area
        ; exit: preserved, or R0+V on error

        Push    "R0-R3,R14"
        ; now we need to find out the save area size for this screen mode
        LDR     R0,=SpriteReason_ReadSaveAreaSize+512
        MOV     R1,R9
        LDR     R2,[R1,#saFirst]
        ADD     R2,R2,R9
        SWI     XOS_SpriteOp
        Pull    "R0-R3,PC",VS

        MOV     R0,#clipboard_flexblock_savearea
        MOV     R1,R3
        BL      clipboard_flex_set_block_size
        ; and we're all done
        Pull    "R0-R3,PC"

clipboard_sprite_area_init
; initialise a sprite area
; In: R1 = sprite area size
;
; Out: all preserved
        Push    "R0-R1,R14"
        MOV     R0,#clipboard_flexblock_sprite
        BL      clipboard_flex_get_block_addr
        STR     R1,[R0,#saEnd]

        MOV     R14,#16
        STR     R14,[R0,#saFirst]

        MOV     R1,R0
        LDR     R0,=SpriteReason_ClearSprites+256
        SWI     XOS_SpriteOp
        Pull    "R0-R1,PC"

clipboard_ensure_sprite_area
; ensure that the sprite area has a sprite big enough for the current writable icon
; In: r4/5 are desired width/height
; Out: V+R0 set if error; r0 corrupt otherwise
        Push    "R0-R9,R14"
        LDR     R8,clipboard_spritearea_addr
        CMP     R8,#0                   ; area set up?
        BNE     %FT01
        BL      clipboard_create_sprite_area ; make a new one
        ADDVS   SP,SP,#4
        Pull    "R1-R9,PC",VS           ; fail on error
01
        ; check block status - if status is 0 we need to sort.
        LDR     R14,[R8,#clipboard_area_status]
        TEQ     R14,#0
        BNE     %FT02

        ; we need to make a new sprite if possible
        BL      clipboard_mode_changed
        ADDVS   SP,SP,#4
        Pull    "R1-R9,PC",VS           ; bail out if we couldn't
02
        ; check current width/height of the sprite in the area
        Push    "R4,R5"                 ; desired width, height - preserve
        MOV     R0,#clipboard_flexblock_sprite
        BL      clipboard_flex_get_block_addr
        MOV     R9,R0
        LDR     R0,=SpriteReason_ReadSpriteSize+512 ; direct pointer to sprite
        MOV     R1,R9
        LDR     R2,[R9,#saFirst]
        ADD     R2,R2,R9
        SWI     XOS_SpriteOp

        Pull    "R6,R7"                 ; recall desired width/height
        ADDVS   SP,SP,#4
        Pull    "R1-R9,PC",VS           ; fail if error getting sprite info

        CMP     R3,R6                   ; widths
        BLT     %FT03                   ; too narrow - need to make a new one
        CMP     R4,R7                   ; heights
        Pull    "R0-R9,PC",GE           ; all good, returns
03
        ; make sure we've enough DA space to do this
        MOV     R4,R6
        MOV     R5,R7
        BL      clipboard_calc_spritearea_size
        MOV     R0,#clipboard_flexblock_sprite
        BL      clipboard_flex_set_block_size
        BVS     %FT60
        BL      clipboard_sprite_area_init ; ensure area reflects new size allocation

        ; recreate the sprite in the desired size, using the saved mode word
        MOV     R0,#clipboard_flexblock_sprite
        BL      clipboard_flex_get_block_addr
        MOV     R9,R0
        LDR     R0,=SpriteReason_CreateSprite+256
        MOV     R1,R9
        ADRL    R2,clipboard_sprite_name
        MOV     R3,#0                   ; no palette
        LDR     R8,clipboard_spritearea_addr
        LDR     R6,[R8,#clipboard_mode_word]
        SWI     XOS_SpriteOp
        BVS     %FT60

        BL      clipboard_setup_save_area
        MOVVC   R14,#1                  ; mark as good area
60
        ; exit
        MOVVS   R14,#0
        STR     R14,[R8,#clipboard_area_status]
        STRVS   R0,[sp]
        Pull    "R0-R9,PC"

clipboard_delete_sprite_area
        ; tidy up - delete dynamic area
        ; In: nothing
        ; Out: V+R0 set if error.
        Push    "R0,R1,R14"
        MOV     R0,#DAReason_Remove
        LDR     R1,clipboard_spritearea_num
        SWI     XOS_DynamicArea
        MOVVC   R0,#0
        STRVC   R0,clipboard_spritearea_num
        STRVC   R0,clipboard_spritearea_addr
        STRVS   R0,[sp]
        Pull    "R0,R1,PC"

clipboard_ensure_icon_space
; ensure enough buffer space for a writable icon, or disable render extension if not possible
; entry: r10=pointer to icon data
; exit:  all preserved
        Push    "R0-R5,R14"

        LDMIA   R10,{R0-R3}             ; read icon bounding box
        SUB     R4,R2,R0
        SUB     R5,R3,R1

        ; gives the result in OS units
        ; convert to pixels...

        LDR     R0,log2px
        MOV     R4,R4,LSR R0
        LDR     R0,log2py
        MOV     R5,R5,LSR R0

        BL      clipboard_ensure_sprite_area
        STRVS   R0,[sp]
        Pull    "R0-R5,PC"

clipboard_output_to_sprite
        Push    "R0-R3,R14"
        LDRB    R14,cnp_buffered
        CMP     R14,#0
        Pull    "R0-R3,PC",NE           ; not needed here

        MOV     R0,#clipboard_flexblock_savearea
        BL      clipboard_flex_get_block_addr
        MOV     R3,R0
        MOV     R0,#clipboard_flexblock_sprite
        BL      clipboard_flex_get_block_addr
        MOV     R1,R0
        LDR     R0,=SpriteReason_SwitchOutputToSprite+512 ; direct sprite pointer
        LDR     R2,[R1,#saFirst]
        ADD     R2,R2,R1
        ; r3 is save area
        MOV     R14,#0
        STR     R14,[R3]                ; reset save area
        SWI     XOS_SpriteOp
        ADDVS   SP,SP,#4
        Pull    "R1-R3,PC",VS

        LDR     R14,clipboard_spritearea_addr
        ADD     R14,R14,#clipboard_prev_status
        STMIA   R14,{R0-R3}
        MOV     R14,#1
        STRB    R14,cnp_buffered
        Pull    "R0-R3,PC"

clipboard_output_restore
        ; restore state
        Push    "R0-R3,R14"
        LDRB    R14,cnp_buffered
        CMP     R14,#0
        Pull    "R0-R3,PC",EQ           ; nothing to do here, move along
        ; set ptr to the space we created in the stack
        LDR     R14,clipboard_spritearea_addr
        ADD     R14,R14,#clipboard_prev_status

        LDMIA   R14,{R0-R3}
        SWI     XOS_SpriteOp

        MOV     R14,#0
        STRB    R14,cnp_buffered

        Pull    "R0-R3,PC"

clipboard_plot_sprite
        ; if there's a sprite, render it to the coords at x0,y0
        Push    "R0-R6,R14"

        ; ensure correct clipping
        Push    "cx0-y1"
        ADR     R14,clipx0
        LDMIA   R14,{cx0,cy0,cx1,cy1}
        ADR     R14,oldclipx0
        STMIA   R14,{cx0,cy0,cx1,cy1}
        max     x0,cx0                  ; NB must result in non-null window
        max     y0,cy0                  ; since the rectangles intersect
        min     x1,cx1
        min     y1,cy1
        BL      graphicswindow
        Pull    "cx0-y1"

        MOV     R4,y0
        MOV     R3,x0
        MOV     R5,#0
        MOV     R0,#clipboard_flexblock_sprite
        BL      clipboard_flex_get_block_addr
        MOV     R1,R0
        LDR     R0,=SpriteReason_PutSpriteUserCoords+512 ; direct sprite pointer
        LDR     R2,[R1,#saFirst]
        ADD     R2,R2,R1
        SWI     XOS_SpriteOp
        ADDVS   SP,SP,#4
        Pull    "R1-R6,PC",VS

        ; restore clip rectangle
        ADR     R14,oldclipx0
        LDMIA   R14,{x0,y0,x1,y1}
        BL      graphicswindow
        Pull    "R0-R6,PC"

; memory management calls
; we use a shifting heap in a dynamic area (unless not supported, in
; which case it's in the RMA instead and not a shfiting heap)

                              ^ 0
clipboard_flex_block_rma_addr # 0       ; if it's an RMA block, address of the block
clipboard_flex_next_offset    # 4       ; if it's a DA, offset to next block
clipboard_flex_block_length   # 4       ; size of this block
clipboard_flex_header_size    # 0

clipboard_flex_create_block
; add block to the heap
; returns block number in R0
; if doing RMA, then R8 is 1+block to reset

        Push    "R1-R3,R14"

      [ UseDynamicArea
        ; find the end of the heap
        MOV     R3,#0
        LDR     R1,clipboard_spritearea_addr
        ADD     R1,R1,clipboard_flex_base
01
        LDR     R2,[R1,#clipboard_flex_next_offset]
        TEQ     R2,#0                   ; end of the heap?
        ADDNE   R1,R1,R2                ; if not, keep looking
        ADDNE   R3,R3,#1
        BNE     %BT01

        ; we now point to the terminator of the heap
        ; extend to give us an empty block
        BL      clipboard_flex_get_alloc_size
        ADD     R0,R0,#clipboard_flex_header_size
        BL      clipboard_flex_set_da_size

        ; create a new block
        MOVVC   R0,#clipboard_flex_header_size
        STRVC   R0,[R1,#clipboard_flex_next_offset]
        MOVVC   R0,#0
        STRVC   R0,[R1,#clipboard_flex_block_length]
        STRVC   R0,[R1,#clipboard_flex_header_size]  ; set a terminator at the end of the block

        MOVVC   R0,R3
        Pull    "R1-R3,PC"
      |
        ; RMA version
        LDR     R1,clipboard_spritearea_addr
        ADD     R1,R1,clipboard_flex_base
        SUB     R3,R8,#1
        ADD     R1,R1,R3,LSL #3
        MOV     R14,#0
        STR     R14,[R1,#clipboard_flex_block_rma_addr]
        STR     R14,[R1,#clipboard_flex_block_length] ; length
        MOV     R0,R8
        Pull    "R1-R3,PC"
      ]

clipboard_flex_get_block_addr
; In: R0=block number
; Out: R0=block address or 0 if not found
        Push    "R1,R2,R14"

      [ UseDynamicArea
        LDR     R1,clipboard_spritearea_addr
        ADD     R1,R1,clipboard_flex_base

        TEQ     R0,#0
        BEQ     %FT02
01
        LDR     R2,[R1,#clipboard_flex_next_offset]
        TEQ     R2,#0                   ; no more blocks
        MOVEQ   R0,#0
        Pull    "R1,R2,PC",EQ

        SUBS    R0,R0,#1
        ADDGE   R1,R1,R2                ; keep trying
        BGE     %BT01
02
        ADD     R0,R1,#clipboard_flex_header_size ; we've found our block

        Pull    "R1,R2,PC"              ; return data address
      |
        ; RMA version
        LDR     R1,clipboard_spritearea_addr
        ADD     R1,R1,clipboard_flex_base
        LDR     R0,[R1,R0,LSL #3]            ; get RMA block address

        Pull    "R1,R2,PC"              ; return data address
      ]

clipboard_flex_get_block_size
; In: R0=block number
; Out: R0=block size or 0 if not found
        Push    "R1,R2,R14"

      [ UseDynamicArea
        LDR     R1,clipboard_spritearea_addr
        ADD     R1,R1,clipboard_flex_base

        TEQ     R0,#0
        BEQ     %FT02
01
        LDR     R2,[R1,#clipboard_flex_next_offset]
        TEQ     R2,#0                   ; no more blocks
        MOVEQ   R0,#0
        Pull    "R1,R2,PC",EQ

        SUBS    R0,R0,#1
        ADDGE   R1,R1,R2                ; keep trying
        BGE     %BT01
02
        LDR     R0,[R1,#clipboard_flex_block_length]

        Pull    "R1,R2,PC"              ; return data address
      |
        ; RMA version
        LDR     R1,clipboard_spritearea_addr
        ADD     R1,R1,clipboard_flex_base
        ADD     R1,R1,R0,LSL #3
        LDR     R0,[R1,#clipboard_flex_block_length]            ; get RMA block address

        Pull    "R1,R2,PC"              ; return data address
      ]

        LTORG

clipboard_flex_get_block_addr_size
; In: R0=block number
; Out: R0=block addr or 0 if not found
;      R1=block length

        Push    "R2,R14"

      [ UseDynamicArea
        LDR     R1,clipboard_spritearea_addr
        ADD     R1,R1,clipboard_flex_base

        TEQ     R0,#0
        BEQ     %FT02
01
        LDR     R2,[R1,#clipboard_flex_next_offset]
        TEQ     R2,#0                   ; no more blocks
        MOVEQ   R0,#0
        MOVEQ   R1,#0
        Pull    "R2,PC",EQ

        SUBS    R0,R0,#1
        ADDGE   R1,R1,R2                ; keep trying
        BGE     %BT01
02
        ADD     R0,R1,#clipboard_flex_header_size
        LDR     R1,[R1,#clipboard_flex_block_length]

        Pull    "R2,PC"
      |
        ; RMA version
        LDR     R1,clipboard_spritearea_addr
        ADD     R1,R1,clipboard_flex_base
        ADD     R2,R1,R0,LSL #3
        LDMIA   R2,{R0,R1} ; get addr, length

        Pull "R2,PC"
      ]

clipboard_flex_set_block_size
; In: R0=block number
;     R1=desired block size
; Out: R0=0 if block not found
;      V set on error

        Push    "R1-R3,R8-R11,R14"

      [ UseDynamicArea
        ADD     R11,R1,#3
        BIC     R11,R11,#3              ; copy the desired size; also make it word aligned

        BL      clipboard_flex_get_block_addr
        TEQ     R0,#0
        Pull    "R1-R3,R8-R11,PC",EQ
        SUB     R10,R0,#clipboard_flex_header_size ; keep block base address

        ; we have the address of the block data
        SUB     R0,R0,#clipboard_flex_header_size ; find the actual header of the block
        LDR     R1,[R0,#clipboard_flex_block_length]
        ADD     R1,R1,#3
        BIC     R1,R1,#3                ; make the current size word aligned
        SUBS    R9,R11,R1               ; r9=change in block size

        LDREQ   R1,[sp]
        STREQ   R1,[R0,#clipboard_flex_block_length] ; length may be different even if size not
        Pull    "R1-R3,R8-R11,PC",EQ    ; block size unchanged, so just quit
        BLT     %FT50                   ; current block size is larger, so we are shrinking

        ; increasing the block size

        ; extend the DA first to make sure there's enough space
        BL      clipboard_flex_get_alloc_size
        ADD     R0,R0,R9                ; new size is old size + our adjustment from above
        BL      clipboard_flex_set_da_size
        Pull    "R1-R3,R8-R11,PC",VS    ; can't extend DA so abort

        ; need to move the data above our block upwards
        ; r10 is our current block
        ; we need to find the end of the heap
        LDR     R0,[R10,#clipboard_flex_next_offset]
        ADD     R1,R10,R0               ; r1 points to the new block
        MOV     R8,R1                   ; keep this in r8
41
        LDR     R0,[R1,#clipboard_flex_next_offset]
        TEQ     R0,#0
        ADDNE   R1,R0,R1
        BNE     %BT41
        ADD     R1,R1,#4                ; allow terminator

        ; move data upwards.  Move data from r8->r1 by r9 bytes

        ADD     R9,R1,R9                ; new top of heap
42
        LDR     R2,[R1,#-4]!
        STR     R2,[R9,#-4]!
        CMP     R8,R1
        BLE     %BT42

        ; adjust the offset pointer for our current block
        LDR     R1,[sp]                 ; get original length
        STR     R1,[R10,#clipboard_flex_block_length]
        ADD     R11,R11,#clipboard_flex_header_size
        STR     R11,[R10,#clipboard_flex_next_offset]

        MOV     R0,#1

        Pull    "R1-R3,R8-R11,PC"
50
        ; shrink the block
        ; move the higher data downwards first

        ; r10 is our current block
        ; we need to find the end of the heap
        LDR     R0,[R10,#clipboard_flex_next_offset]
        ADD     R1,R10,R0               ; r1 points to the new block
        MOV     R8,R1                   ; keep this in r8
51
        LDR     R0,[R1,#clipboard_flex_next_offset]
        TEQ     R0,#0
        ADDNE   R1,R0,R1
        BNE     %BT51
        ADD     R1,R1,#4                ; allow terminator

        ; move data downwards.  Move data from r8->r1 by r9 bytes
        ADD     R9,R8,R9                ; destination pointer in r9; r9 is lower than r8
52
        LDR     R2,[R8],#4
        STR     R2,[R9],#4
        CMP     R8,R1
        BNE     %BT52

        ; adjust the offset pointer for our current block
        LDR     R1,[sp]                 ; get original block length
        STR     R1,[R10,#clipboard_flex_block_length]
        ADD     R11,R11,#clipboard_flex_header_size
        STR     R11,[R10,#clipboard_flex_next_offset]

        ; shrink the DA if possible
        BL      clipboard_flex_get_alloc_size
        BL      clipboard_flex_set_da_size

        MOVVC   R0,#1

        Pull    "R1-R3,R8-R11,PC"
      |
        ; RMA version
        LDR     R11,clipboard_spritearea_addr
        ADD     R11,R11,clipboard_flex_base
        ADD     R11,R11,R0,LSL #3

        LDMIA   R11,{R8,R9} ; get current addr,length

        TEQ     R1,R9
        Pull    "R1-R3,R8-R11,PC",EQ ; nothing to do - no change in size

        TEQ     R8,#0 ; no current addr
        BEQ     %FT50

        ; we have a block already
        ; realloc or free?
        MOV     R10,R1
        TEQ     R1,#0
        MOVEQ   R0,#ModHandReason_Free
        MOVNE   R0,#ModHandReason_ExtendBlock
        MOV     R2,R8
        SUBNE   R3,R1,R9
        SWI     XOS_Module
        Pull    "R1-R3,R8-R11,PC",VS

        TEQ     R10,#0
        STRNE   R2,[R11,#clipboard_flex_block_rma_addr]
        STREQ   R10,[R11,#clipboard_flex_block_rma_addr]
        STR     R10,[R11,#clipboard_flex_block_length]

        Pull    "R1-R3,R8-R11,PC"
50
        ; no current block, so claim one if we can
        TEQ     R1,#0
        Pull    "R1-R3,R8-R11,PC",EQ ; nothing to do if there's no size
        MOV     R3,R1
        MOV     R0,#ModHandReason_Claim
        SWI     XOS_Module
        STMVCIA R11,{R2,R3} ; addr, length

        Pull    "R1-R3,R8-R11,PC"
      ]

      [ UseDynamicArea
clipboard_flex_get_alloc_size
; Out: R0=current required size of the DA
        Push    "R1-R3,R14"
        LDR     R1,clipboard_spritearea_addr
        MOV     R3,R1                   ; keep base address for later
        ADD     R1,R1,#clipboard_flex_base
01
        LDR     R2,[R1,#clipboard_flex_next_offset]
        TEQ     R2,#0                   ; any more blocks?
        ADDNE   R1,R1,R2
        BNE     %BT01                   ; still going
        SUB     R0,R1,R3                ; make a length
        ADD     R0,R0,#4                ; add length of terminator

        Pull    "R1-R3,PC"

clipboard_flex_set_da_size
; In: R0 = desired size
; Out: If error, R0 and V set
        Push    "R0-R4,R14"
        MOV     R3,R0
        LDR     R4,clipboard_spritearea_num
        MOV     R0,R4
        SWI     XOS_ReadDynamicArea     ; get current size
        ADDVS   SP,SP,#4
        Pull    "R1-R4,PC",VS

        MOV     R0,R4
        SUB     R1,R3,R1
        SWI     XOS_ChangeDynamicArea   ; request the new size - may result in area shrinkage
        STRVS   R0,[SP]
        Pull    "R0-R4,PC"
      ]
 ]
        END