Scrollen in alle Richtungen

Nachdem ich bei einem Freund eine Maschinen-Routine sah, die es aus GFA-Basic heraus ermöglicht [mit dem Befehl CALL adr()], schnell pixelweise horizontal auf dem Bildschirm ‘um die Uhr’ zu scrollen, wurde ich neugierig: so etwas suchte ich, und eigene Versuche brachten bisher nicht das gewünschte Ergebnis. Allerdings ist diese Routine ziemlich ‘tricky’ programmiert, und es ist auch nur möglich, nach links zu scrollen.

Das Trickreiche an der Routine ist, daß, um Übergabeparameter zu sparen, die den Aufruf ja auch verlangsamen, in einer Initialisierungsroutine die Adresse der Scroll-Routine geladen wurde, und mittels Offset die für das Scrollen nötigen Parameter an die Stelle in der Scroll-Routine ‘geschoben’ wurden, an der sie benötigt werden. Werden nun in der Scroll-Routine Zeilen geändert, hinzugefügt oder gelöscht, kann sich der Offset ändern, was schöne Abstürze mit sich bringt. Genau die Probleme hatte ich bei dem Versuch, die Assembler-Routine in C einzubinden, da LASER an den Anfang jeder Funktion den LINK-Befehl des 68000 und eventuell den MOVEM-Befehl zum Registerretten setzt. Daher überlegte ich mir eine elegantere und auch nicht langsamere Methode. Die zum Scrollen nötigen Variablen werden in einer Struktur zusammengefaßt, und ein Zeiger darauf wird den Scroll-Routinen übergeben. Dadurch entfällt eine weitere Einschränkung; werden nämlich direkt in der Routine Werte geändert, braucht man für jeden zu scrollenden Block eine eigene Routine, bzw. es müßte jedes Mal die Initialisierungsroutine aufgerufen werden, wenn ein anderer Block gescrollt werden soll, was nicht besonders zur Geschwindigkeits-Steigerung beiträgt.

Die Struktur namens SCROLLER besteht aus folgenden Komponenten: words enthält die Wörter (16 Bit) pro Zeile, zeilen die Zeilenzahl, beide um 1 erniedrigt (spart im Assembler je eine Zeile), offset enthält die Breite in Bytes, wird zur Korrektur des an der abzuarbeitenden Zeile langlaufenden Zeigers benötigt, adr ist die Adresse auf dem Monitor, pixel die Breite in Pixeln, wird nur für Links-rechts-Scrollen benötigt.

Den Scroll-Routinen wird neben dem Zeiger auf die Struktur ein Wert (long warten) für die Pause übergeben, die nach jeder Zeile beim vertikalen bzw. nach jedem Pixel beim horizontalen Scrollen gemacht werden soll, die Zahl der Zeilen bzw. Pixel (int pixel), die pro Aufruf der Routine gescrollt werden sollen, und ein Flag für die Richtung (int richtung, 1 = links/oben, 0 = rechts/unten). warten verlangsamt das Scrollen absolut, auch für die anderen scrollenden Bereiche, während pixel das Scrollen des einen relativ zu den anderen ändert. So kann z.B ein Block bei jedem Durchlauf 4 Pixel scrollen, während die anderen nur 1 Pixel pro Durchlauf scrollen dürfen.

Die Routinen etwas näher betrachtet: scroll_lr(): Als erstes wird dem Compiler das Übertragen der Strukturkomponenten in die Register abgenommen! LASER hat nämlich die unangenehme Eigenschaft, vor jedem Zugriff auf eine Komponente die Adresse der Struktur in A0 zu laden und dann relativ zu adressieren, statt die Adresse nur einmal zu laden. Da die Scroll-Routinen nun etwas öfters aufgerufen werden, machen sich 3 zusätzliche MOVEA-Zeilen bemerkbar. Aber dafür gibt es ja den praktischen Inline-Assembler. Die erste Schleife zählt die Zahl der Zeilen pro Durchgang herunter (die relative Geschwindigkeit), als nächstes kommt die Pause. Adresse und Zeilenzahl werden in die Register geladen, auf die Richtung geprüft und zum entsprechenden Teil verzweigt. Das ganz linke bzw. rechte Wort wird mit dem roxr/roxl-Befehl gerollt und der Zeiger auf den Anfang bzw. das Ende der Zeile gesetzt, danach wird der Rest der Zeile wortweise gerollt.

scroll_ou(): Es werden 3 Zeiger verwendet, in Al liegt die Adresse der ersten Zeile (beim Hoch-Scrollen) bzw. der letzten Zeile, last_line zeigt auf die letzte bzw. erste Zeile. Dadurch lassen sich die erste und die letzte Zeile nach jedem Durchlauf (wenn also alle Zeilen 1 Zeile verschoben wurden) einfach tauschen, lauf ist ein Zeiger auf die jeweils aktuelle Zeile, die verschoben wird. Da A1 und last_line nach jedem Durchgang um Bytes verschoben sind, werden sie in den Variablen anfang und ende gebuffert, um Rechenzeit zu sparen. Vor jedem Durchgang (label z1) werden die Adressen neu geladen, es folgen wie beim horizontalen Scrollen die Zeilenschleife und die absolute Pause. Die wortweise Verschiebeschleife für die Zeilen (wo bzw. wu) kann durch Verwendung eines festen Offsets sehr kurz ausfallen (läuft daher auch nur mit 640 Pixeln Breite). Nun wird der laufende Zeiger korrigiert und auf die nächste Zeile gesetzt. Nach Verschieben aller Zeilen wird noch die erste Zeile gegen die letzte getauscht (labels ll bzw. fl), das war es dann auch schon.

Die Funktion scrollinit() errechnet die für das Scrollen nötigen Parameter, wobei für die Richtungen links und oben der Zeiger auf den Anfang des Blocks, für rechts und unten auf das Ende gesetzt wird. Zu beachten ist, daß das Flag, das die Richtung bestimmt, den gleichen Wert haben muß wie beim Aufruf der Scroll-Routinen, da sonst falsche Bereiche gescrollt werden. Es findet auch keine Überprüfung auf negative Werte statt, die entstehen, wenn man (x2/y2) kleiner als (x1/y1) übergibt, also Vorsicht!

Im Source sind an einigen Stellen Pfeile (==>), die deutlich machen, wo die Routinen an die Bildschirmbreite angepaßt werden sollten, damit wenigstens monochrom auf größeren Bildschirmen gescrollt werden kann.

Die Routinen sind so geschrieben, daß sie ohne globale Zugriffe auskommen, es müssen nur die beiden Strukturen vereinbart werden, was ja in einer Header-Datei geschehen kann. Extra compiliert braucht man das File dann nur noch hinzuzulinken. Natürlich sind die Routinen weniger geeignet, wenn es darum geht, den ganzen Bildschirm zu verschieben. Die wortweise Schleife ist dann einfach zu langsam. Für kleinere Bereiche (und dann auch gerne mehrere) lassen sich aber doch recht flüssige Bewegungen erzielen.

Besondere Effekte erzielt man beim Übereinanderlegen von scrollenden Blöcken. Rechts + Oben = Diagonal, das ist ja wohl klar; überkreuzen sich z.B. 2 Blöcke, die in die gleiche Richtung scrollen. wird der überlappende Bereich auseinandergezogen, scrollen sie entgegengesetzt, scheint der Schnittpunkt stillzustehen. Weisen die beiden auch noch unterschiedliche relative Geschwindigkeiten auf, läuft der Schnittpunkt plötzlich spiegelverkehrt! Ein nettes Feld zum Experimentieren ergibt sich auch, wenn 2 senkrechte und 2 waagerechte ‘Stäbe’ so eingestellt sind, daß die Pixel im Kreis (bzw. im Rechteck) laufen.

Eine ‘ernsthaftere’ Anwendung ist z.B. eine Laufschrift, wie im Beispiel-Source. Vielleicht kommt ja mal jemand auf die Idee, wichtige Alert-Boxen mit scrollenden Stopschildem auszustatten? Die werden dann bestimmt nicht mehr übersehen!

Bei dem Demoprogramm sind einige Tasten des Zehnerblocks belegt: ‘+’ und ‘-’ erhöhen bzw. erniedrigen die relative Geschwindigkeit der horizontalen Schrift, ’*’ und ’/’ die der vertikal scrollenden Schrift. ‘(‘ und ')’ erhöhen/erniedrigen die absolute Geschwindigkeit (Pause), Enter setzt die Parameter zurück. Mit ‘ESC’ wird das Programm verlassen.

/* SCROLL - ROUTINEN für MEGAMAX LASER C */ 
/*          von Ulrich Witte             */
/*      (c) 1991 MAXON Computer          */

#include <osbind.h>
#include "scroll.h"

scroll_lr(scroll,warten,pixel,rxchtung) 
int pixel;
register int richtung; 
long warten;
SCROLLER *scroll;
{
    register char *quelle; 
    register int zeilen; 
    register int words; 
    register long offset; 
    asm 
    {
        movea.l scroll(A6),A0   ;Adr der Struktur 
        move.w  (A0),words      ;Vars aus Struktur in 
                                ;in Register schieben
        move.l  2(A0),offset 
        move.w  6(A0),zeilen 
        movea.l 8(A0),quelle    ;Adresse des Blocks
        move.w  pixel(A6),D2
        subq.w  #1,D2
anzahl: move.l  warten(A6),D3
pause1: dbf     D3,pause1       ;Pause
                                ; Scrollgeschwxndxgkext absolut 
        movea.l quelle,A0
        move.w  zeilen,D1
        cmpi.w  #1,richtung     ;TRUE = links herum
        beq.s   links1
rechts1:move.w  -2(A0),D0 
        roxr.w  #1,D0
        move.w  words,D0        ;Worte pro Zeile 
        suba.l  offset,A0       ;Ptr auf Zeilenanf. 
rechts2:roxr.w  (A0)+           ;alle Bytes 1
                                ;Pixel weiterrollen
        dbf     D0,rechts2
        suba.l  #80,A0          ;==> 1 Zeile höher
        dbf     D1,rechts1
        dbf     D2,anzahl
        bra.s   ende            ;das war's schon

links1: move.w  (A0),D0
        roxl.w  #1,D0
        move.w  words,D0
        adda.l  offset,A0
links2: roxl.w  -(A0)
        dbf     D0,links2
        adda.l  #80,A0          ;=> nächste Zeile

        dbf     D1,links1
        dbf     D2,anzahl

ende:
    }
}

scroll_ou(scroll,warten,anzahl,richtung) 
int anzahl;
register int richtung; 
long warten;
SCROLLER *scroll;
{
    register int zeilen;
    register int words,
    register long offset;
    register char *anfang,*ende,*lauf,*last_line;

    asm
    {
        movea.l scroll(A6),A0   ;Adr. der Struktur
        move.w  (A0),words 
        move.l  2(A0),offset 
        move.w  6(A0),zeilen 
        movea.l 8(A0),A1        ;Adresse des Blocks
        movea.l A1,last_line 
        move.w  zeilen,D0       ;==> Zeilenzahl * 80 
        mulu.w  #80,D0          ;==> anpassen
        cmpi.w  #1,richtung 
        beq.s   add
        suba.l  D0,last_line    ;Adr der 1. Zeile
        bra.s   cont 
add:    adda.l  D0,last_line    ;Adr der letzten 
                                ;Zeile für Hochsrollen
cont:   movea.l A1,anfang       ;Anfangs-Adr buffern
        movea.l last_line,ende  ;last_line buffern
        move.w  anzahl(A6),D3   ;Gesamtdurchläufe
        subq.w  #1,03 
z1:     move.w  zeilen,D1       ;1. Schleife
                                ;Gesamtdurchläufe
        movea.l anfang,lauf     ;Laufptr auf Anfang
        movea.l anfang,A1       ;Adr. der ersten 
                                ;und letzten Zeile laden 
        movea.l ende,last_line 
z:      move.l  warten(A6),D0   ;2. Schleife
                                ;zeilenweise 
pause:  dbf     D0,pause        ;Pause...
        move.w  words,D2        ;Worte pro Zeile
        cmpi.w  #1,richtung 
        beq.s   wo              ; nach unten. 
wu:     move.w  -82(lauf),-(lauf) ;==> 3.Schleife 
    ;Jede Zeile wortweise eine Zeile tiefer schieben
        dbf     D2,wu
        adda.l  offset,lauf 
        suba.l  #80,lauf        ;==> nächste Zeile
        dbf     D1,z
        move.w  words,D2        ;letzte Zeile in erste
ll:     move.w  -(A1),-(last_line)
        dbf     D2,ll
        dbf     D3,zl
        bra.s   end             ;fertig
                                ;nach oben: 
wo:     move.w  80(lauf),(lauf)+ ;==>
    ;jede Zeile wortweise eine Zeile hoher schieben
        dbf     D2,wo
        suba.l  offset,lauf
        adda.l  #80,lauf        ;==> nächste Zeile
        dbf     D1,z
        move.w  words,D2
fl:     move.w  (A1)+,(last_line)+
                                ;erste Zeile in die letzte Zeile 
        dbf     D2,fl
        dbf     D3,zl
end:
    }
}

scrollinit(k,scroll,links_oder_oben) 
register SCROLLER *scroll; 
register RECT *k; 
int links_oder_oben;
{
    register int help;
/* x-Koordinaten geteilt durch 16 ergibt */ 
/* Anzahl der Worte pro Zeile */
/* Die x-Koordinaten der Struktur */
/* dürfen nicht geändert werden */ 
    register int x1 = k->x1 » 4; 
    register int x2 = k->x2 » 4;

/* Monitor-Adresse holen */
    scroll->adr = (char *)Physbase();
/* Differenz merken */ 
    help = x2 - x1; 
    if (links_oder_oben)
/* ==> Adresse auf linke obere Ecke setzen */
        scroll->adr += (x1 «1) + (k->y1 * 80);
    else
/* ==> oder rechts unten als Adresse eintragen */ 
        scroll->adr += (x2 « 1) + ((k->y2 - 1) * 80); 
    scroll->words = help - 1;
/* Bytes = Worte * 2 */
    scroll->offset = (long)(help « 1);
/* Seilen - 1 */
    scroll->zeilen = k->y2 - k->y1 - 1;
/* Pixelzahl = Differenz * 16 */ 
    scroll->pixel = help « 4,
}
/* Header für Scrollroutinen */

/* Struktur für die 4 Eckpunkte des zu */
/* scrollenden Bereichs, wird */
/* für die Initialisierung der Parameter */ 
/* der SCROLLER-Struktur benötigt */

typedef struct
{
    int x1; 
    int y1; 
    int x2; 
    int y2;
}RECT;

/* Struktur, die zum Scrollen */
/* nötigen Parameter enthalt */

typedef struct
{
    int words;      /* Wortbreite des Bereichs */ 
    long offset     /* Breite in Bytes */ 
    int zeilen;     /* Höhe in Pixelzeilen */
    char *adr;      /* Adresse auf dem Monitor */ 
    int pixel;      /* Breite in Pixel,wird für */
                    /* Links-Rechts benötigt */
}SCROLLER;

/*  Demo-Programm für die Scrollroutinen    */
/*          von Ulrich Witte                */
/*      (c) 1991 MAXON Computer             */

#include <osbind.h>
#include "scroll.h"

int contrl[12]; /* Globale VDI-Variablen */ 
int intin[256], ptsin[256];
int intout [256], ptsout[256];

SCROLLER s[10];

main()
{
    int taste;
        /* Flag für Richtung */ 
    int links_oder_oben = 1;
        /* Scrollgeschwindigkeit relativ , zu */
        /* scrollende Pixel pro Durchlauf */ 
    int x = 1,y = 1;
        /* Scrollgeschwindigkeit absolut, */
        /* Pause pro Durchlauf */ 
    long pause = 0L;
        /* Nummer der Workstation */ 
    int handle;

        /* GEM Bescheid sagen, daß wir da sind */ 
    appl_init();
        /* VDI-Handle besorgen */ 
    handle = open_workstation();
        /* schnell die Maus ausmachen */ 
    asm {dc.w 0xa00a}
        /* und den Bildschirm säubern */ 
    v_clrwk(handle);

        /* ein bisschen Text und Grafik */ 
    mach_was_auf_den_bildschirm(handle); 
    scroll_bereiche_festlegen(links_oder_oben);

    while ((taste = Crawio(0xff) & 0xff ) != 27)
    {       /* solange nicht ESC gedrückt */
        switch (taste)
        {
            case 32:    /* Space */
                links_oder_oben ^= 1;
    /* Flag umdrehen, neue Initialisierung */
        scroll_bereiche_festlegen(links_oder_oben); 
        break;
            case 0x2b:  /* '+' auf Zehnertastatur */ 
                if (++x >= 10) 
                    x = 10; 
                break; 
            case 0x2d: /* '-' */
                if (--x <= 1) 
                    x = 1; 
                break; 
            case 0x2a: /* '*' */
                if (++y >= 10) 
                    y = 10; 
                break; 
            case 0x2f; /* '/' */
                if (—-y <= 1) 
                    y = 1;
                break;
            case 0x28: /* '(' */
                if (++pause >= 1000L) 
                    pause = 1000L; 
                break; 
            case 0x29: /* ')' */
                if (—-pause <= 0L) 
                    pause = 0L; 
                break;
            case 0xd: /* 'Enter' */
                /* Parameter zurucksetzen */ 
                pause = 0L; 
                x = y = 1; 
                break;
        }
    /* scroll_lr(&s[3],pause,x,!links_oder_oben);*/ 
       scroll_ou(&s[7],pause,y,links_oder_oben);

       scroll_lr(&s[0],pause,x,links_oder_oben);
    /* scroll_ou(&s[4],pause,y,!links_oder_oben);*/

       scroll_lr(&s[2],pause,1,!links_oder_oben);
    /* scroll_ou(&s[6],pause,1,!links_oder_oben);*/

            /* Diagonal */ 
       scroll_lr(&s[1],pause,1,!links_oder_oben); 
       scroll_ou(&s[5],pause,1,links_oder_oben);

    /* scroll_ou(&s[8],pause,1,1);*/

    }
    asm {dc.w 0xa009} /* Maus wieder an */ 
    y_clsvwk(handle); /* Workstation schließen */ 
    appl_exit(); /* Bei GEM abmelden */
    Pterm0(); /* und raus; nur nötig, wenn in */ 
/* der INIT-C des LASER der Sprung zur Exit- */ 
/* Routine entfernt wird (spart ca 3 Kb !) */
}

open_workstation()
{
    register int x;
    int work_in[11],work_out[57],handle,dummy;

/* work_in Array fur GEM initialisieren */
    for(x=0; x < 10; work_in[x++] = 1);
    work_in[10] = 2;
    handle = graf_handle(&dummy,&dummy,&dummy,&dummy);
    v_opnvwk(work_in, &handle, work_out); 
    return handle;
}

mach_was_auf_den_bildschirm(handle) 
register int handle,
{
    int dummy, rect[4];

    vst_height(handle,26,&dummy,&dummy,&dummy,&dummy); 
    v_gtext(handle, 128,100,"LEA.L _LAUFSCHRIFT,A0");

    vst_height(handle,13,&dummy,&dummy,&dummy,&dummy); 
    v_gtext(handle,100, 150, "Dieser String scrollt in die andere Richtung ");

    vswr_mode(handle,2);
    vsf_color(handle, 1); /* Farbe schwarz */ 
    vsf_interior(handle,3); /* Strichmuster */ 
    vsf_style(handle,8);

    rect_set(rect,159,199,208,380); 
    v_bar(handle,rect);

    vsf_interior(handle, 2) ; /* Punktmuster */
    vsf_style(handle,4);
    rect_set(rect,319,299,560,330);
    v_bar(handle,rect);
    v_gtext(handle, 330,320,"Z W E I BOXEN, die Scrollen!");

    vst_rotation(handle, 900);
    vst_height(handle,6,&dummy,&dummy,&dummy,&dummy);
/* v_gtext(handle,40,350,"Das kommt doch auch nicht schlecht, oder?");*/
}

scroll_bereiche_festlegen(links_oder_oben) 
register int links_oder_oben;
{
    RECT koor;

/* 2 Bereiche mit gleichen Koordinaten */
/* erlauben Diagonal Scrolling, wenn */
/* beide nacheinander gescrollt werden */

    rect_set(&koor,128,73,500,106); 
    scrollinit(&koor,&s[0],links_oder_oben); 
    scrollinit(&koor,&s[4],!links_oder_oben);

    rect_set(&koor,320,300,560,330); 
    scrollinit(&koor,&s[1],!links_oder_oben); 
    scrollinit(&koor,&s[5],links_oder_oben);

    rect_set(&koor,160,200,208,380); 
    scrollinit(&koor,&s[2],!links_oder_oben); 
    scrollinit(&koor,&s[6],!links_oder_oben);

    rect_set(&koor,100,133,474,154); 
    scrollinit(&koor,&s[3],!links_oder_oben); 
    scrollinit(&koor,&s[7],links_oder_oben);

    rect_set(&koor,32,20,48,350); 
    scrollinit(&koor,&s[8],links_oder_oben);
}

Ulrich Witte
Aus: ST-Computer 11 / 1991, Seite 80

Links

Copyright-Bestimmungen: siehe Über diese Seite