;
; ptr.s
;
; Pointer changing and caret blinking (TMA)
;
;  1994-1998 Straylight
;

;----- Licensing note -------------------------------------------------------
;
; This file is part of Straylight's Sapphire library.
;
; Sapphire is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2, or (at your option)
; any later version.
;
; Sapphire is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with Sapphire.  If not, write to the Free Software Foundation,
; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

;----- Standard header ------------------------------------------------------

		GET	libs:header
		GET	libs:swis

;----- External dependencies ------------------------------------------------

		GET	sapphire:event
		GET	sapphire:except
		GET	sapphire:idle
		GET	sapphire:resspr
		GET	sapphire:sapphire
		GET	sapphire:screen
		GET	sapphire:sprite
		GET	sapphire:string
		GET	sapphire:wimp

;----- Main code ------------------------------------------------------------

		AREA	|Sapphire$$Code|,CODE,READONLY

;----- Pointer shape changing -----------------------------------------------

; --- ptr_setShape ---
;
; On entry:	R0 == sprite name
;		R1 == x offset of hot spot
;		R2 == y offset of hot spot
;
; On exit:	--
;
; Use:		Set the pointer sprite to the given name. The sprite area
;		used is that returned from resspre_area.

		EXPORT	ptr_setShape
ptr_setShape	ROUT

		STMFD	R13!,{R0-R7,R12,R14}	;Stack some registers
		WSPACE	ptr__wSpace		;Get my workspace

		; --- Try to find a good match for the mode ---

		MOV	R1,R0			;Get name in R1
		BL	screen_getInfo		;Get mode information block
		MOV	R7,R0			;Remember its position
		MOV	R0,R11			;Put my buffer in R0
		BL	str_cpy			;Copy the string across

		MOV	R4,#'0'			;For ASCII conversion
		LDR	R1,[R7,#screen_dx]	;Get x pixel size
		ADD	R1,R4,R1		;Convert to ASCII pix size
		STRB	R1,[R0],#1		;Store it in the buffer
		LDR	R1,[R7,#screen_dy]	;Get y pixel size
		ADD	R1,R4,R1		;Convert to ASCII pix size
		STRB	R1,[R0],#1		;Store it in the buffer
		MOV	R1,#0
		STRB	R1,[R0]			;Stick a NULL on the end

		; --- Does sprite exist (sprite call corrupts R0-R6) ---

		MOV	R0,#40			;Read sprite information
		MOV	R2,R11			;Pointer to sprite name
		BL	sprite_op		;Does the sprite exist

		LDMIA	R13,{R2,R4,R5}		;Get users name, x and y
		BVS	%10ptr_setShape		;If sprite didn't exist...

		; --- Now use the new sprite ---

		MOV	R2,R11			;Use this sprite name

		; --- Alter the x and y offsets for new resolution ---

		LDR	R0,[R7,#screen_xEig]	;Get the screen xEig
		MOV	R4,R4,LSL #1		;Multiply by 2
		MOV	R4,R4,LSR R0		;And shift down by xEig
		LDR	R0,[R7,#screen_yEig]	;Get the screen yEig
		MOV	R5,R5,LSL #2		;Multiply by 4
		MOV	R5,R5,LSR R0		;And shift down by yEig

10ptr_setShape	MOV	R0,#36			;Set pointer shape
		MOV	R3,#2			;Pointer shape number
		MOV	R6,#0			;Scale factors
		MOV	R7,#0			;Pixel translation

		BL	sprite_op		;Do the correct SpriteOp

		; --- Alter my flags ---

		LDR	R0,ptr__flags		;Get my flags
		ORR	R0,R0,#ptr__USERSET+ptr__NOTDEFAULT
		STR	R0,ptr__flags		;Store the flags back

		; --- Return to caller ---

		LDMFD	R13!,{R0-R7,R12,PC}^	;Return to user

		LTORG

; --- ptr_resetShape ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Resets the pointer shape to the default.

		EXPORT	ptr_resetShape
ptr_resetShape	ROUT

		STMFD	R13!,{R0,R12,R14}	;Save some registers
		WSPACE	ptr__wSpace		;Get my workspace

		; --- Is the default pointer already on? ---

		LDR	R0,ptr__flags		;Get my flags
		TST	R0,#ptr__NOTDEFAULT	;Are we using default
		LDMEQFD	R13!,{R0,R12,PC}^	;Yes, return

		; --- Alter the flags ---

		BIC	R0,R0,#ptr__NOTDEFAULT+ptr__USERSET
		STR	R0,ptr__flags		;Store the flags

		; --- Now reset the shape using *pointer (Yuck) ---

		ADR	R0,ptr__pointer		;Point to command
		SWI	OS_CLI			;Execute it

		LDMFD	R13!,{R0,R12,PC}^	;Return to user

		LTORG

ptr__pointer	DCB	"Pointer",0		;*Pointer command string

;----- Automatic pointer changing -------------------------------------------

; --- ptr__idles ---
;
; On entry:	R12 == pointer to my workspace
;
; On exit:	--
;
; Use:		Called on idle events to try to change the pointer
;		shape if it needs to.

ptr__idles	ROUT

		STMFD	R13!,{R0-R7,R14}	;Stack some registers

		LDR	R0,ptr__flags		;Get my flags
		TST	R0,#ptr__USERSET	;Are we using user ptr?
		LDMNEFD	R13!,{R0-R7,PC}^	;Yes, return

		; --- Is the pointer over an icon? ---

		MOV	R1,R11			;Use the scratch pad
		SWI	Wimp_GetPointerInfo	;Get pointer info
		LDR	R0,[R1,#16]		;Get icon under pointer
		LDR	R2,ptr__oldIcon		;What were we over before?
		STR	R0,ptr__oldIcon		;This is now previous icon
		CMP	R2,R0			;Are they the same
		LDMEQFD	R13!,{R0-R7,PC}^	;Yes -- return
		CMP	R0,#-1			;Is it the window background?
		BEQ	%90ptr__idles		;Yes -- skip to the end

		; --- Is this icon interesting? ---

		STR	R0,[R1,#4]		;Store the icon number
		LDR	R2,[R1,#12]		;Get the returned window hnd
		STR	R2,[R1,#0]		;Store it at beginning
		SWI	Wimp_GetIconState	;Get the state of the icon
		LDR	R2,[R1,#24]		;Get the icon flags

		MOV	R7,R2,LSR#12		;Get the button type
		AND	R7,R7,#15		;...only

		TST	R2,#&100		;Is it indirected?
		TSTNE	R2,#&01			;Is it a text icon?
		BEQ	%90ptr__idles		;Not both -- return
		AND	R2,R2,#&1f0000		;Get the ESG number
		CMP	R2,#&1f0000		;Is it 31?
		BEQ	%90ptr__idles		;Yes -- it's shaded

		LDR	R2,[R1,#32]		;Get the validation string
		CMP	R2,#-1			;Does it exist?
		BEQ	%90ptr__idles		;No -- return

		; --- Now parse validation string for xp<name>,<x>,<y> ---
		;
		; This is based on a state drive parser with the following
		; defined states:
		;
		; 	0 == not in anything useful
		;	1 == looking for xp
		;	2 == reading sprite name
		;	3 == reading x value
		;	4 == reading y value
		;	5 == finished

		MOV	R0,#1			;Set the current state to 1
		ADD	R3,R11,#20		;Use this buffer
		MOV	R5,#0			;Current x value
		MOV	R6,#0			;Current y value

		; --- All cases return to here ---

00ptr__idles	CMP	R0,#5			;Are we in state 5?
		BEQ	%70ptr__idles		;Yes -- we're finished
		LDRB	R1,[R2],#1		;Get a character
		CMP	R1,#31			;Is it a terminator
		BLE	%70ptr__idles		;Yes -- we're finished

		; --- case '\' ---

		CMP	R1,#'\'			;Is it the escape character?
		BNE	%10ptr__idles		;No -- try next case
		LDRB	R4,[R2]			;Get the next byte
		CMP	R4,#31			;Is it a terminator
		MOVGT	R1,R4			;No -- use this
		ADDGT	R2,R2,#1		;...and increment pointer
		CMP	R0,#1			;Are we in state 1?
		MOVEQ	R0,#0			;Yes -- go into state 0
		CMP	R0,#2			;Are we in state 2
		STREQB	R1,[R3],#1		;Yes -- store the character
		B	%00ptr__idles		;break

		; --- case ';' ---

10ptr__idles	CMP	R1,#';'			;Is it a new command?
		BNE	%20ptr__idles		;No -- try next case
		CMP	R0,#1			;Is the state greater than 1?
		MOVGT	R0,#5			;Yes -- state = 5
		MOVLE	R0,#1			;No -- state = 1
		B	%00ptr__idles		;break

		; --- case 'X' / 'x' ---

20ptr__idles	CMP	R1,#'X'			;Is is an 'X'?
		CMPNE	R1,#'x'			;Or an 'x'?
		BNE	%30ptr__idles		;No -- try next case
		CMP	R0,#1			;Are we in state 1?
		BNE	%21ptr__idles		;No -- go ahead a bit
		LDRB	R4,[R2]			;Get next character
		CMP	R4,#'P'			;Is it a 'P'?
		CMPNE	R4,#'p'			;Or a 'p'?
		ADDEQ	R2,R2,#1		;Yes -- increment pointer
		MOVEQ	R0,#2			;...and set state to 2
		B	%00ptr__idles		;break

21ptr__idles	CMP	R0,#2			;Are we in state 2?
		STREQB	R1,[R3],#1		;Yes -- store the character
		B	%00ptr__idles		;break

		; --- case ',' ---

30ptr__idles	CMP	R1,#','			;Is it a ,?
		BNE	%40ptr__idles		;No -- try next case
		CMP	R0,#2			;Are we in state 2?
		MOVEQ	R4,#0			;Yes -- get the NULL byte
		STREQB	R4,[R3],#1		;... store it at end of name
		CMPNE	R0,#3			;Or state 3?
		CMPNE	R0,#4			;Or state 4?
		ADDEQ	R0,R0,#1		;If any, increment the state
		B	%00ptr__idles		;break

		; --- default ---

40ptr__idles	CMP	R0,#1			;Are we in state 1?
		MOVEQ	R0,#0			;Yes -- put it in state 0
		BEQ	%00ptr__idles		;break
		CMP	R0,#2			;State 2?
		STREQB	R1,[R3],#1		;Yes -- store the character
		BEQ	%00ptr__idles		;break
		CMP	R0,#3			;Are we in state 3?
		CMPNE	R0,#4			;Or 4?
		BNE	%00ptr__idles		;No -- break
		CMP	R1,#'0'			;Is it less than '0'
		BLT	%00ptr__idles		;Yes -- break
		CMP	R1,#'9'			;Is it greater than '9'
		BGT	%00ptr__idles		;Yes -- break
		SUB	R1,R1,#'0'		;Turn it into a number
		MOV	R4,#10			;A useful number
		CMP	R0,#3			;Are we reading x?
		MLAEQ	R5,R4,R5,R1		;Yes -- calculate new x
		MLANE	R6,R4,R6,R1		;No -- calculate new y
		B	%00ptr__idles		;break

		; --- We have finished parsing the string ---

70ptr__idles	CMP	R0,#1			;Is the state > 1?
		BLE	%80ptr__idles		;No -- try default case
		ADD	R0,R11,#20		;Point to sprite name
		MOV	R1,R5			;The x value
		MOV	R2,R6			;The y value
		BL	ptr_setShape		;Set the pointer shape
		LDR	R14,ptr__flags		;Get the new flags
		BIC	R14,R14,#ptr__USERSET	;The user didn't set it
		STR	R14,ptr__flags		;Store them again
		LDMFD	R13!,{R0-R7,PC}^	;Return

		; --- If the button type was writable, use caret_ptr ---

80ptr__idles	CMP	R7,#14			;Writable?
		CMPNE	R7,#15
		BNE	%90ptr__idles		;Return
		ADR	R0,ptr__caretPtr	;The sprite name
		MOV	R1,#4			;X offset
		MOV	R2,#5			;Y Offset
		BL	ptr_setShape		;Set the pointer shape
		LDR	R14,ptr__flags		;Get the new flags
		BIC	R14,R14,#ptr__USERSET	;The user didn't set it
		STR	R14,ptr__flags		;Store them again
		LDMFD	R13!,{R0-R7,PC}^	;Return

		; --- Return to the user ---

90ptr__idles	BL	ptr_resetShape		;Reset the pointer
		LDMFD	R13!,{R0-R7,PC}^	;...and return

		LTORG

; --- ptr__postFilter ---
;
; On entry:	R0 == event code returned
;		R1 == pointer to block returned
;		R12 == pointer to my workspace
;
; On exit:	--
;
; Use:		Called as a post-filter to trap pointer entering/leaving
;		events, so that idles may be added for pointer
;		changing.

ptr__postFilter	ROUT

		; --- Ensure that we want this event ---

		CMP	R0,#4			;Pointer leaving?
		BEQ	%50ptr__postFilter	;Yes -- handle that
		CMPNE	R0,#5			;Or entering?
		MOVNES	PC,R14			;Neither, return now

		; --- Pointer is entering one of tasks windows ---

		STMFD	R13!,{R0-R3,R14}	;Stack some registers
		MOV	R0,#2			;Call it this frequently
		ADR	R1,ptr__idles		;Call this on idle events
		MOV	R2,#0			;Our user handle
		MOV	R3,R12			;Put our workspace in R12
		BL	idle_handler		;Add the idle handler
		MOV	R0,#-1			;Set up previous icon number
		STR	R0,ptr__oldIcon		;...to a non positive value
		LDMFD	R13!,{R0-R3,PC}^	;Return to caller

		; --- Pointer is leaving a window ---

50		STMFD	R13!,{R0-R3,R14}	;Stack some registers
		MOV	R0,#2			;Call it this frequently
		ADR	R1,ptr__idles		;Call this on idle events
		MOV	R2,#0			;Our user handle
		MOV	R3,R12			;Put our workspace in R12
		BL	idle_removeHandler	;Remove the handler routine
		LDR	R14,ptr__flags		;Load my flags word
		TST	R14,#ptr__USERSET	;Is it the user's pointer?
		BLEQ	ptr_resetShape		;No -- clear pointer shape
		LDMFD	R13!,{R0-R3,PC}^	;And return to caller

		LTORG

ptr__caretPtr	DCB	"ptr_caret",0

;----- Caret blinking -------------------------------------------------------

; --- ptr__doCaret ---
;
; On entry:	R0 == flags word
;		R1 == pointer to block to use
;
; On exit:	--
;
; Use:		Turn the caret on or off, according to the relevent bit
;		in the flags word.

ptr__doCaret	ROUT

		MOV	R5,R0			;Remember flags word
		SWI	Wimp_GetCaretPosition	;Get the caret position
		LDR	R0,[R1,#0]		;Window handle
		LDR	R2,[R1,#8]		;X offset
		LDR	R3,[R1,#12]		;Y offset
		LDR	R4,[R1,#16]		;Caret height and flags

		; --- Set or clear the 'invisible' bit ---

		TST	R5,#ptr__ON		;Turn the caret on?
		ORREQ	R4,R4,#1<<25		;No, set the 'invisible' bit
		BICNE	R4,R4,#1<<25		;No, clear 'invisible' bit

		; --- Set the caret position ---

		LDR	R5,[R1,#20]		;Index into string
		LDR	R1,[R1,#4]		;Icon handle

		SWI	Wimp_SetCaretPosition	;Put back invisible caret

		MOVS	PC,R14			;Return to caller

		LTORG

; --- ptr__blinkCaret ---
;
; On entry:	R12 == workspace pointer
;
; On exit:	--
;
; Use:		Called by an alarm to flash the caret.

ptr__blinkCaret	ROUT

		STMFD	R13!,{R0-R5,R14}	;Stack some registers

		; --- Do I own the task the carets in? ---

		MOV	R1,R11			;Point to scratchpad
		SWI	Wimp_GetCaretPosition	;Get the caret position
		LDR	R2,[R1,#0]		;Get window handle
		CMP	R2,#-1			;Is it in a window?
		BEQ	%00			;No -- set up alarm, return

		; --- Send a acknowledgement message around ---

		MOV	R0,#20			;Message size
		STR	R0,[R1,#0]		;Store in message block
		MOV	R0,#0			;Your ref
		STR	R0,[R1,#0]		;Store in message block
		MOV	R0,#19			;Send message_acknowlegde
		SWI	Wimp_SendMessage	;Send the message

		; --- My task handle is now in R2 ---

		BL	wimp_taskHandle		;Get the actual task handle
		CMP	R0,R2			;Do we own caret?
		BNE	%00			;No -- set up alarm, return

		; --- Mess about withe my flags, and blink caret ---

		LDR	R0,ptr__flags		;Get my flags word
		TST	R0,#ptr__ON		;Is the caret on?
		BICNE	R0,R0,#ptr__ON		;The caret is now off
		ORREQ	R0,R0,#ptr__ON		;The caret is now on
		STR	R0,ptr__flags		;Store flags back

		BL	ptr__doCaret		;Blink the caret

		; --- Prepare another alarm ---

00		BL	ptr__setUpAlarm		;Prepare to flash again

		; --- And return to caller ---

		LDMFD	R13!,{R0-R5,PC}^	;Stack some registers

		LTORG

; --- ptr__setUpAlarm ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Sets up an alarm to blink the caret

ptr__setUpAlarm	ROUT

		STMFD	R13!,{R14}		;Stack return address
		SWI	OS_ReadMonotonicTime	;Get the current time
		ADD	R0,R0,#25		;Add 1/25 of a second
		ADR	R1,ptr__blinkCaret	;Call this routine
		ADR	R2,ptr__setUpAlarm	;My private handle
		MOV	R3,R12			;Pass this for R12
		BL	idle_setAlarm		;Set up the alarm
		LDMFD	R13!,{PC}		;Return to caller

		LTORG

; --- ptr_blinkOn ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Makes the caret blink while it is in a window owned by your
;		application.

		EXPORT	ptr_blinkOn
ptr_blinkOn	ROUT

		STMFD	R13!,{R0-R3,R12,R14}	;Stack some registers
		WSPACE	ptr__wSpace		;Point to my workspace

		; --- Turn on caret blinking ---

		LDR	R14,ptr__flags		;Get my flags
		TST	R14,#ptr__BLINKING	;Is blinking on?
		LDMNEFD	R13!,{R0-R3,R12,PC}^	;Yes -- return

		ORR	R14,R14,#ptr__BLINKING	;Set the 'is blinking' bit
		STR	R14,ptr__flags		;Store the flags word

		BL	ptr__setUpAlarm		;Set up the blinking alarm

		; --- Return to client ---

		LDMFD	R13!,{R0-R3,R12,PC}^	;And return

		LTORG

; --- ptr_blinkOff ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Turns the caret blinking off.

		EXPORT	ptr_blinkOff
ptr_blinkOff	ROUT

		STMFD	R13!,{R0-R5,R12,R14}	;Stack some registers
		WSPACE	ptr__wSpace		;Point to my workspace

		; --- Is blinking already off? ---

		LDR	R14,ptr__flags		;Get my flags
		TST	R14,#ptr__BLINKING	;Is blinking on?
		LDMEQFD	R13!,{R0-R5,R12,PC}^	;Yes -- return

		BIC	R14,R14,#ptr__BLINKING	;Clear the 'is blinking' bit
		STR	R14,ptr__flags		;Store the flags word

		; --- Remove any blink alarms already set up ---

		ADR	R0,ptr__setUpAlarm	;My private handle
		BL	idle_removeAllAlarms	;Remove all alarms I own

		; --- Update my flags appropriately ---

		LDR	R0,ptr__flags		;Get my flags word
		ORR	R0,R0,#ptr__ON		;Caret is on
		STR	R0,ptr__flags		;Store the flags back
		MOV	R1,R11			;Point to scratchpad
		BL	ptr__doCaret		;Turn the caret off

		; --- Return to client ---

		LDMFD	R13!,{R0-R5,R12,PC}^	;And return

		LTORG

;----- Initialisation -------------------------------------------------------

; --- ptr_init ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Initialises the ptr system.

		EXPORT	ptr_init
ptr_init	ROUT

		STMFD	R13!,{R0,R1,R12,R14}	;Stack some registers
		WSPACE	ptr__wSpace		;Get my workspace

		; --- Are we already initialised? ---

		LDR	R0,ptr__flags		;Get my flags
		TST	R0,#ptr__INITED		;Are we initialised?
		LDMNEFD	R13!,{R0,R9,PC}^	;Yes -- return

		ORR	R0,R0,#ptr__INITED+ptr__ON ;Set flags
		STR	R0,ptr__flags		;And store them back

		; --- Ensure that the event system is initialised ---

		BL	event_init

		; --- And set up a post-filter for pointer changing ---

		ADR	R0,ptr__postFilter	;Address of routine to call
		MOV	R1,R12			;Call with my workspace
		BL	event_postFilter	;And add to the post filters

		; --- Set up exit handler ---

		BL	except_init		;Make sure except is awake
		ADR	R0,ptr_resetShape	;Make pointer normal on exit
		MOV	R1,R12			;Pass workspace in R12
		BL	except_atExit		;Register the routine

		; --- That's it now ---

		LDMFD	R13!,{R0,R1,R12,PC}^	;Return

		LTORG

ptr__wSpace	DCD	0			;My workspace pointer

;----- Workspace ------------------------------------------------------------

		^	0,R12
ptr__wStart	#	0

ptr__flags	#	4			;Flags

ptr__INITED	EQU	(1<<0)			;I've been initialised
ptr__BLINKING	EQU	(1<<1)			;Caret blinking is on
ptr__ON		EQU	(1<<2)			;Caret is on
ptr__NOTDEFAULT	EQU	(1<<3)			;We're not using default ptr
ptr__USERSET	EQU	(1<<4)			;The user set the pointer

ptr__oldIcon	#	4			;Icon we were previously over

ptr__wSize	EQU	{VAR}-ptr__wStart

		AREA	|Sapphire$$LibData|,CODE,READONLY

		DCD	ptr__wSize		;Workspace size
		DCD	ptr__wSpace		;Workspace pointer
		DCD	40			;Scratchpad size
		DCD	ptr_init		;Initialisation code

;----- That's all, folks ----------------------------------------------------

		END
