← ST-Computer 12 / 1988

Nachlauffreies Scrolling

Programmierpraxis

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)
  • | Betriebssystem | SCROLL ------- | ---------- | ---------- Darstellen von 10 kompletten Bildschirm-Seiten | | | 4.8 | 1.1 Scrollen in einem Text, 1000 Zeilen | | Blitter-TOS, mit Blitter | | Nach oben | 37.8 | 22.9 Nach unten | 37.4 | 22.8 Blitter-TOS, ohne Blitter | | Nach oben | 49.8 | 22.9 Nach unten | 46.3 | 22.8

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