Nachlauffreies Scrolling

Bei dieser Routine handelt es sich um ein Assembler-Progrämmchen, das es erlaubt, auch aus Hochsprachen (BASIC, C, Pascal, Modula) ein absolut nachlauffreies Text-Scrolling zu realisieren. Um ein großes Maß an Flexibilität zu gewährleisten, teilt sich die Routine in drei verschiedene Teile ein.

Als erstes gibt es einen Programmteil, der einen ganzen Bildschirm (25 Zeilen) auf einmal ausgibt.

An dieser Stelle kommt natürlich kein Text-Scrolling zum Tragen, vielmehr macht sich hier die Geschwindigkeit der Routine beim Ausgeben von Strings bemerkbar. Ca. 0.1 Sekunde muß für einen ganzen Schirm einkalkuliert werden. Diese Zeit ist etwa um den Faktor 4.5 gegenüber der normalen Textausgabe über das Betriebssystem schneller. Des weiteren erledigen zwei andere Programmteile das Scrolling nach oben und unten. Auch hier sorgen die Routinen für einen richtigen Turbo-Effekt, und störendes Flimmern, wie etwa bei den Betriebssystem-Routinen, kann man lange suchen.

Nun aber zum Aufruf des Assembler-Bröckchens. Wie aus dem Beispiel-Programm zu ersehen, verwende ich die Routinen in Verbindung mit GFA-BASIC.

Deshalb werde ich auch meine Erklärungen in der Syntax dieser Sprache halten. Um jedoch auch den Freunden anderer Sprachen die Möglichkeit zum Verständnis zu geben, möchte ich zunächst den hier verwendeten Befehl “C:adr%(L:string%,W:mode%)” erläutern. Mit seiner Hilfe lassen sich von GFA-BASIC aus Maschinen-Routinen aufrufen. Dazu wird in der Variablen adr% die Adresse der entsprechenden Routine abgelegt. In string% findet sich beim Aufruf der beiden Scroll-Prozeduren die Adresse des auszugebenden Strings wieder. Dieser String sollte normalerweise die Länge von 80 Zeichen = einer Bildschirmzeile besitzen. Um jedoch auch kürzere Strings ausgeben zu können, läßt sich das Ende des Strings mit einem Null-Byte markieren. Der Rest des Bildschirms hinter dem Null-Byte wird dann gelöscht.

Der Parameter mode% (Wortlänge) bestimmt dabei die aufzurufende Funktion. Bei einer negativen Zahl (-1) wird der Bildschirm nach unten verschoben und der String in der obersten Zeile dargestellt.

Es wird also nach oben gescrollt. Genau die umgekehrte Richtung, nämlich nach unten, nimmt die Routine, wenn mode% auf einen positiven Wert gesetzt wird. Der Schirm wird dann nämlich nach oben verschoben und der String erscheint am unteren Rand Ihres Monitors.

Wenn Sie nun einen ganzen Bildschirm darstellen möchten, müssen Sie zuerst eine Tabelle erstellen, in der die Adressen aller auszugebender Strings aufgeführt sind. Wie man das in GFA-BASIC erledigt, kann man sich in Listing 1 ansehen.

An die Scroll-Routine übergeben Sie dann nicht mehr die Adressen der einzelnen Strings, sondern die Adresse dieser Tabelle. Für die Länge der Strings gelten die gleichen Bedingungen wie bei den eigentlichen Scroll-Routinen. Wie Sie bei dem Aufruf der Routine schon sehen können, ist der Parameter mode% in diesem Falle auf Null zu setzen.

Es ist nicht nötig, vor dem Aufruf den Mauszeiger oder den Cursor auszuschalten. Diese Aufgaben erledigt die Routine ohne Ihr Zutun.

Nach diesen Erläuterungen zur Benutzung nun aber weiter zur Arbeitsweise.

Als erstes werden der Maus- und der Textcursor ausgeschaltet, danach die Adressen des Font-Images und des logischen Bildschirmes ermittelt. Das Font-Image enthält die Daten, aus denen sich die einzelnen Zeichen zusammensetzen. Ein Zeichen belegt dort 16 Byte. Da es im Zeichensatz des ATARI 256 verschiedene Zeichen des 8*16 Systemfonts gibt, ist diese Tabelle folglich 4096 Bytes lang. Die einzelnen Bytes des Zeichens liegen dabei immer um 256 Bytes verschoben in diesem Image. Will man also z.B. ein “A” ausgeben (ASCII-Code 65), muß zur Adresse des Font-Images noch diese 65 addiert werden, und schon ergibt sich die Adresse der ersten Pixelzeile des Zeichens. Die weiteren Pixelzeilen folgen um je- weils 256 Bytes versetzt. Da direkt auf den Zeichensatz zugegriffen wird, ist es möglich, alle Zeichen aus dem Zeichensatz darzustellen. Somit lassen sich auch die Steuercodes kleiner als 32 in Form von Symbolen ausgeben.

Danach wird, in Abhängigkeit vom Parameter mode%, in die einzelnen Teile der Scroll-Routine verzweigt. Diese Programmteile rufen dann, eventuell nach dem Scrollen des Bildschirms, die Routine “AUSGABE” auf. Dort wird ein String auf den Bildschirm geschrieben. Schließlich werden dann der Maus- und Textcursor wieder sichtbar gemacht und die Routine verlassen.

Um das Prinzip besser zu verstehen und um diese Routinen in der Anwendung zu sehen, habe ich ein kleines Programm geschrieben, das ein Gerüst für einen Text-Editor darstellt (s. Listing 2).

# Einige Benchmarks: (Zeiten in Sekunden)

Benchmarks

a$=""	! a$ löschen
FOR a%=anfang% to anfang%2	! Zählschleife
	a$=a$+MKL$(VARPTR(feld$(a%))) ! Tab.d.Adr.erstellen 
NEXT a%	! nächst. Feldelement
tab%=VARPTR(a$)	! Adr.d.Tab.ermitteln
a%=C:adr%(L:tab%,0)	! Routine aufrufen

Listing 1: Ermitteln der Adressen der auszugebenden Strings

' © 1988 Martin Fangmeyer 
'	Wilmeresch	60
'	4430 Steinfurt 1
'
RESTORE scroll	! Datazeiger setzen
READ len%		! Länge der Routine	lesen
scroll$=SPACE$(len%*4)	! Speicher für die Routine reservieren
scroll%=VARPTR(scroll$) ! Adr.d.Speichers holen 
FOR a%=1 TO len%
	READ wert%
	LPOKE scroll%,wert% ! Routine i.d.Speicher (L)poken
	ADD scroll%, 4		! Nächstes Langwort
NEXT a%
'
DIM text$(1000)			! Stringfeld dimensionieren
FOR a%=0 TO 1000		! Dieses Feld mit zufälligem Inhalt füllen 
	text$(a%)=LEFT$(STR$(a%)+" "+STRING$(78,RANDOM (223)+32),80)
NEXT a%
'
a$=SPACE$(100)	! Platz	für	25*4 Byte (eine Bildschirmseite) 
adr%=VARPTR(a$)	! Adresse des Strings ermitteln

FOR a%=0 TO 24	! Die Adressen der Text-Strings festhalten 
	LPOKE adr%+a%*4,VARPTR(text$(a%))
NEXT a%
'
scroll%=VARPTR(scroll$)	! Die Adresse der Scroll-Routine holen 
VOID C:scroll%(L:adr%,0)	! Die erste Textseite ausgeben
PRINT CHR$(27);CHR$(101);	! Cursor einschalten
REPEAT
	a%=INP(2)				! Auf Tastendruck warten
	IF a%=200				! Hoch (scrollen)
		DEC zeile%
		IF zeile%<0				! Eventuell bei 1000 neu anfangen
			zeile%=1000
		ENDIF
		IF CRSLIN=1				! Scrollen notwendig
			scroll%=VARPTR(scroll$)
			VOID C:scroll%(L:VARPTR(text$(zeile%)),-1)
			! Scroll-Routine aufrufen
		ELSE
			PRINT CHR$(27);CHR$(65);	! Andernfalls Cursor eins höher
		ENDIF
	ELSE
		IF a%=208			! Runter (scrollen)
			INC zeile%
			IF zeile%>1000	! Evtl,	bei 0 neu anfangen
				CLR zeile%
			ENDIF
			IF CRSLIN=25	! Scrollen	notwendig
				scroll%=VARPTR(scroll$)
				VOID C:scroll%(L:VARPTR(text$(zeile%)),1)
				! Scrollroutine aufrufen
			ELSE
				PRINT CHR$(27);CHR$(66);	!Sonst nur Cursor höher
			ENDIF
		ELSE
			IF a%=20	! Links
				PRINT CHR$(27);CHR$(68);	! Cursor eine Pos nach links
			ELSE
				IF a%=205	! Rechts
					PRINT CHR$(27);CHR$(67);	! Cursor eine nach rechts
				ELSE	! Taste ist kein Steuerzei-
					OUT 2,a%	! Daher das Zeichen auf dem Schirm ausgeben
				ENDIF
			ENDIF
		ENDIF
	ENDIF
UNTIL a%=27	! Solange,	bis	ESC gedrückt
'
scroll:
DATA 152
DATA 809238555,1627390504,809238630, 1627390496, -1610595334,37364392 
DATA -1400822,1060896771,1313756303,1140458028, 578833007,551680 
DATA 9725696,18620480,-772014080,83914815, 1289240318,1223180030 
DATA -87012136,1056852200,1056897744,1289240318, 1223180030,-87012136 
DATA 1056852200,1056897744,1289240318,1223180030, -87012136,1056852200
DATA 1056897744,1289240318,1223180030,-87012136, 1056852200,1056897744 
DATA 1289240318,1223180030,-87012136,1056852200, 1056897744,1372127130 
DATA 745472004,611975588,-704905216,2013275196,79, 1627390136,1610613116 
DATA 541118972,30240,1883196632,1056852200, 1056834768,1289240318 
DATA 1223180030,80760024,1056852200,1056834768, 1289240318,1223180030 
DATA 80760024,1056852200,1056834768,1289240318, 1223180030,80760024 
DATA 1056852200,1056834768,1289240318,1223180030, 80760024,1056852200 
DATA 1056834768,1289240318,1223180030,80777724,960, 1372127124
DATA 745472004,611975440,607911936,5202218, 1610612976,574357504 
DATA 1584128,544145412,608578648,607911936,5202190, -591659008
DATA 83907017,-1286144,13247098,14041728,270420480, 1734792128
DATA 345052521,16777296,359203328,10491241, 50331888,359203840 
DATA 20977001,83886480,359204352,31462761, 117441072,359204864 
DATA 41948521,150995664,359205376,52434281, 184550256,359205888 
DATA 62920041,218104848,359206400,73405801, 251659440,1380602314 
DATA -7188875,1108492842,5259818,10502698,15745578, 20988458,26231338 
DATA 31474218,36717098,41959978,47202858,52445738, 57688618,62931498 
DATA 68174378,73417258,78664330,1372258238, 1316306688,1060896770 
DATA 1060896771,1313692815,1316302908,1794538, 809238629,1642373129 
DATA 1316290560,0,0,0,0

Listing 2: Kleines Grundgerüst für einen Text-Editor

	MOVE #27,D0 
	BSR BCONOUT
	MOVE #102,D0	; Cursor ausschalten
	BSR BCONOUT
	DC.W $A000		; LINE-A installieren
	LEA CHARS(PC),A1 ; Umständlich, aber PC-relativ
	MOVE.L -$16(A0),(A1) ; Adresse des Zeichensatzes speichern
	DC.W $A00A		; Maus ausschalten

	MOVE #3,-(SP)
	TRAP #14 
	ADDQ.L #2,SP
				; In D0 findet man nun die Bildschirm-Adresse
	LEA LOGBASE(PC),A1
	MOVE.L D0,(A1)	; Bildschirmadresse speichern 
	TST 8 (SP)		; Flag:
	BMI RAUF		; Kleiner als 0: Raufscrollen
	BEQ SEITE		; Gleich 0: Ganze Seite darstellen
	MOVE.L D0,A0	; Ansonsten: Runterscrollen
	ADD.L #1280,A0	; 1. Zeile löschen
	MOVEQ.L #63,D0	; 64 Durchläufe => 24 Zeilen bewegen
SCROLL_DOWN:
	MOVEM.L (a0)+,D1-D7/A1-A5 ; mit 2 Zeilen werden 48 Byte kopiert 
	MOVEM.L D1-D7/A1-A5,-1328(A0) ; 1 
	MOVEM.L (a0)+,D1-D7/A1-A5 
	MOVEM.L D1-D7/A1-A5,-1328(a0) ; 2 
	MOVEM.L (a0)+,D1-D7/A1-A5 
	MOVEM.L D1-D7/A1-A5,-1328(a0) ; 3 
	MOVEM.L (a0)+,D1-D7/A1-A5 
	MOVEM.L D1-D7/A1-A5,-1328(a0) ; 4 
	MOVEM.L (a0)+,D1-D7/A1-A5 
	MOVEM.L D1-D7/A1-A5,-1328(a0) ; 5 
	MOVEM.L (a0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,-1328(a0) ; 6 
	MOVEM.L (a0)+,D1-D7/A1-A5 
	MOVEM.L D1-D7/A1-A5,-1328(a0) ; 7 
	MOVEM.L (a0)+,D1-D7/A1-A5 
	MOVEM.L D1-D7/A1-A5,-1328(a0) ; 8 
	MOVEM.L (a0)+,D1-D7/A1-A5 
	MOVEM.L D1-D7/A1-A5,-1328(a0) ; 9 
	MOVEM.L (a0)+,D1-D7/Al-A5 
	MOVEM.L D1-D7/A1-A5,-1328(a0) ; 10 
	DBRA D0,SCROLL_DOWN 	; insgesamt: 64*10*48=30720 Byte kopieren
	MOVE.L 4(SP),A6			; Adresse des Strings vom Stack holen
	MOVE.L LOGBASE(PC),A2	; Bildschirmadresse in A2
	ADD.L #30720,A2			; Nur unterste Zeile schreiben 
	MOVE.L #79,D2			; 80 Zeichen
	BSR AUSGABE				; Zeile ausgeben
	BRA ZURÜCK 				; Und zurück zum aufrufenden Programm
RAUF:						; Raufscrollen
	MOVE.L D0,A0			; Bildschirmadresse	in	A0
	ADD.L #30240,A0			; Unterste Zeile löschen
	MOVEQ #63,D0			; 64 Durchläufe (0 zählt mit)

SCROLL_UP:
	MOVEM.L (A0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,1232(A0)	;	1
	MOVEM.L (A0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,1232(A0)	;	2
	MOVEM.L (A0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,1232(A0)	;	3
	MOVEM.L (A0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,1232(A0)	;	4
	MOVEM.L (A0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,1232(A0)	;	5
	MOVEM.L (A0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,1232(A0)	;	6
	MOVEM.L (A0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,1232(A0)	;	7
	MOVEM.L (A0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,1232(A0)	;	8
	MOVEM.L (A0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,1232(A0)	;	9
	MOVEM.L (A0)+,D1-D7/A1-A5
	MOVEM.L D1-D7/A1-A5,1232(A0)	;	10
	SUB.L #960,A0
	DBRA D0,SCROLL_UP		; 64*10*48=30720 Byte kop.
	MOVE.L 4(SP),A6			; Adresse des Strings
	MOVE.L LOGBASE(PC),A2	; Bildschirmadresse
	MOVE.L #79,D2			; 80 Zeichen
	BSR.S AUSGABE			; Oberste Zeile schreiben
	BRA ZURÜCK				; Zurück zum aufrufenden Programm

SEITE:	; Eine ganze Seite schreiben
	MOVE.L #24,D1			; 25 Bildschirmzeilen 
	MOVE.L D0,D6			; Bildschirmadr.in D6 speichern 
	MOVE.L 4(SP),A0 		; Adresse der Stringtabelle 25*4 Byte

NÄCHSTE_ZEILE:
	MOVE.L D6,A2	;	Bildschirmadresse
	MOVE.L (A0)+,A6	;	eine Zeile holen
	MOVE.L #79,D2	;	80 Buchstaben
	BSR.S AUSGABE	;	eine Zeile ausgeben
	ADD.L #1280,D6	; eine Cursorzeile tiefer
	DBRA D1,NÄCHSTE_ZEILE ; nächste Zeile
	BRA ZURÜCK 		; Zurück zum aufrufenden Programm

AUSGABE:
	MOVE.L CHARS(PC),A1 ; Adr.d.Zeichensatzdaten 
	CLR.L D0
	MOVE.B (A6)+,D0		; Ein Byte holen
	TST.B D0			; Byte testen
	BEQ.S ZEILE_ZU_ENDE ; Zeile zu Ende (Byte =0)? 
	ADD.L D0,A1			; + Offset
	MOVE.B (A1),(A2)	; Zeichen auf den Bildschirm bringen
	MOVE.B 256(A1),80(A2)
	MOVE.B 512(A1),160(A2)
	MOVE.B 768(A1),240(A2)
	MOVE.B 1024(A1),320(A2)
	MOVE.B 1280(A1),400(A2)
	MOVE.B 1536(A1),480(A2)
	MOVE.B 1792(A1),560(A2)
	MOVE.B 2048(A1),640(A2)
	MOVE.B 2304(A1),720(A2)
	MOVE.B 2560(A1),800(A2)
	MOVE.B 2816(A1),880(A2)
	MOVE.B 3072(A1),960(A2)
	MOVE.B 3328(A1),1040(A2)
	MOVE.B 3584(A1),1120(A2)
	MOVE.B 3840(A1),1200(A2)	; 16 MOVE-Befehle

NÄCHSTES_ZEICHEN:
	ADDQ #1,A2			;	eine Zeichenposition weiter
	DBRA D2,AUSGABE 	; und nächstes Zeichen ausgeben 
	RTS

ZEILE_ZU_ENDE:			; Nach einem Null-Byte wird die

	CLR.B (A2)	; Zeile bis zum rechten Rand gelöscht
	CLR.B 80(A2)
	CLR.B 160(A2)
	CLR.B 240(A2)
	CLR.B 320(A2)
	CLR.B 400(A2)
	CLR.B 480(A2)
	CLR.B 560(A2)
	CLR.B 640(A2)
	CLR.B 720(A2)
	CLR.B 800(A2)
	CLR.B 880(A2)
	CLR.B 960(A2)
	CLR.B 1040(A2)
	CLR.B 1120(A2)
	CLR.B 1200(A2)
	ADDQ.L #1,A2
	DBRA D2,ZEILE ZU_ENDE ; Weiter bis zum rechten Rand
	RTS

BCONOUT:	; ein Zeichen 'normal' ausgeben, z.B.
			; um den Cursor ein- und auszuschalten 			
	MOVE D0,-(SP)	; Zeichen auf den Stack
	MOVE #2,-(SP)	; 'Zielgerät' Bildschirm (Console)
	MOVE #3,-(SP)	; Funktionsnummer
	TRAP #13		; Bios aufrufen
	ADDQ.L #6,SP	; Stack reparieren
	RTS

ZURÜCK:	;	Hier kehrt die Routine zu Basic zurück
	MOVE #27,D0		; Escape-Sequenz einläuten
	BSR.S BCONOUT
	MOVE #101,D0	; Cursor einschalten
	BSR.S BCONOUT
	DC.W $A009		; Maus wieder einschalten
	RTS

CHARS:	DS.L	1	; Speicher für Adresse des Zeichensatzes 
LOGBASE:	DS.L 1	; Speicher für Bildschirmadr.

END

Listing 3: Die Assembler-Routine


Martin Fangmeyer
Aus: ST-Computer 12 / 1988, Seite 84

Links

Copyright-Bestimmungen: siehe Über diese Seite